watermarked_camera.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import 'dart:io';
  2. import 'dart:typed_data';
  3. import 'dart:ui' as ui;
  4. import 'package:flutter/material.dart';
  5. import 'package:image_picker/image_picker.dart';
  6. import 'package:intl/intl.dart';
  7. import 'package:path_provider/path_provider.dart';
  8. import 'package:chicken_farm/core/utils/logger.dart';
  9. class WatermarkedCamera extends StatefulWidget {
  10. final Function(File)? onImageCaptured;
  11. final String? watermarkText;
  12. const WatermarkedCamera({
  13. super.key,
  14. this.onImageCaptured,
  15. this.watermarkText,
  16. });
  17. @override
  18. State<WatermarkedCamera> createState() => _WatermarkedCameraState();
  19. }
  20. class _WatermarkedCameraState extends State<WatermarkedCamera> {
  21. XFile? _capturedImage;
  22. File? _watermarkedImage;
  23. bool _isProcessing = false;
  24. Future<void> _takePicture() async {
  25. try {
  26. final ImagePicker picker = ImagePicker();
  27. final XFile? photo = await picker.pickImage(
  28. source: ImageSource.camera,
  29. imageQuality: 80,
  30. );
  31. if (photo != null) {
  32. setState(() {
  33. _capturedImage = photo;
  34. _isProcessing = true;
  35. });
  36. // 添加水印并处理图像
  37. await _addWatermark(photo);
  38. }
  39. } catch (e) {
  40. if (mounted) {
  41. ScaffoldMessenger.of(
  42. context,
  43. ).showSnackBar(SnackBar(content: Text('拍照失败: $e')));
  44. }
  45. logger.e('拍照失败: $e');
  46. }
  47. }
  48. Future<void> _addWatermark(XFile imageFile) async {
  49. try {
  50. // 获取水印文本
  51. String watermark =
  52. widget.watermarkText ??
  53. DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now());
  54. // 将图片转换为字节以便处理
  55. final Uint8List imageBytes = await imageFile.readAsBytes();
  56. // 创建图片对象
  57. final ui.Codec codec = await ui.instantiateImageCodec(imageBytes);
  58. final ui.FrameInfo frameInfo = await codec.getNextFrame();
  59. final ui.Image image = frameInfo.image;
  60. // 创建画布
  61. final ui.PictureRecorder recorder = ui.PictureRecorder();
  62. final ui.Canvas canvas = ui.Canvas(recorder);
  63. // 绘制原始图片
  64. canvas.drawImage(image, Offset.zero, ui.Paint());
  65. // 添加水印文字
  66. final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
  67. ui.ParagraphStyle(
  68. textAlign: TextAlign.left,
  69. fontSize: 24.0,
  70. textDirection: ui.TextDirection.ltr,
  71. ),
  72. );
  73. paragraphBuilder.pushStyle(
  74. ui.TextStyle(
  75. color: const ui.Color(0x80FFFFFF), // 半透明白色
  76. fontSize: 24.0,
  77. ),
  78. );
  79. paragraphBuilder.addText(watermark);
  80. final ui.Paragraph paragraph = paragraphBuilder.build()
  81. ..layout(ui.ParagraphConstraints(width: image.width.toDouble()));
  82. // 在右下角绘制水印
  83. canvas.drawParagraph(
  84. paragraph,
  85. Offset(
  86. image.width.toDouble() - paragraph.width - 20,
  87. image.height.toDouble() - 40,
  88. ),
  89. );
  90. // 完成绘制
  91. final ui.Picture picture = recorder.endRecording();
  92. final ui.Image watermarkedImage = await picture.toImage(
  93. image.width,
  94. image.height,
  95. );
  96. // 转换为字节数据
  97. final ByteData? byteData = await watermarkedImage.toByteData(
  98. format: ui.ImageByteFormat.png,
  99. );
  100. if (byteData != null) {
  101. // 保存到临时文件
  102. final tempDir = await getTemporaryDirectory();
  103. final file = File(
  104. '${tempDir.path}/watermarked_${DateTime.now().millisecondsSinceEpoch}.png',
  105. );
  106. await file.writeAsBytes(byteData.buffer.asUint8List());
  107. setState(() {
  108. _watermarkedImage = file;
  109. _isProcessing = false;
  110. });
  111. } else {
  112. setState(() {
  113. _isProcessing = false;
  114. });
  115. if (mounted) {
  116. ScaffoldMessenger.of(
  117. context,
  118. ).showSnackBar(const SnackBar(content: Text('处理图片失败')));
  119. }
  120. logger.e('处理图片失败');
  121. }
  122. } catch (e) {
  123. setState(() {
  124. _isProcessing = false;
  125. });
  126. if (mounted) {
  127. ScaffoldMessenger.of(
  128. context,
  129. ).showSnackBar(SnackBar(content: Text('添加水印失败: $e')));
  130. }
  131. logger.e('添加水印失败: $e');
  132. }
  133. }
  134. void _confirmImage() {
  135. if (_watermarkedImage != null && widget.onImageCaptured != null) {
  136. widget.onImageCaptured!(_watermarkedImage!);
  137. }
  138. Navigator.of(context).pop();
  139. }
  140. void _retakePicture() {
  141. setState(() {
  142. _capturedImage = null;
  143. _watermarkedImage = null;
  144. });
  145. }
  146. @override
  147. Widget build(BuildContext context) {
  148. return Scaffold(
  149. appBar: AppBar(
  150. title: const Text('拍照签到'),
  151. leading: IconButton(
  152. icon: const Icon(Icons.close),
  153. onPressed: () => Navigator.of(context).pop(),
  154. ),
  155. ),
  156. body: Column(
  157. children: [
  158. Expanded(
  159. child: _capturedImage == null
  160. ? Center(
  161. child: Column(
  162. mainAxisAlignment: MainAxisAlignment.center,
  163. children: [
  164. const Icon(
  165. Icons.camera_alt,
  166. size: 100,
  167. color: Colors.grey,
  168. ),
  169. const SizedBox(height: 20),
  170. const Text('点击按钮拍照', style: TextStyle(fontSize: 18)),
  171. ],
  172. ),
  173. )
  174. : _isProcessing
  175. ? const Center(child: CircularProgressIndicator())
  176. : Center(
  177. child: _watermarkedImage != null
  178. ? Image.file(_watermarkedImage!)
  179. : Image.file(File(_capturedImage!.path)),
  180. ),
  181. ),
  182. Padding(
  183. padding: const EdgeInsets.all(16.0),
  184. child: Row(
  185. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  186. children: [
  187. if (_capturedImage == null)
  188. FloatingActionButton(
  189. onPressed: _takePicture,
  190. child: const Icon(Icons.camera),
  191. )
  192. else if (!_isProcessing)
  193. Row(
  194. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  195. children: [
  196. ElevatedButton(
  197. onPressed: _retakePicture,
  198. style: ElevatedButton.styleFrom(
  199. backgroundColor: Colors.grey,
  200. foregroundColor: Colors.white,
  201. ),
  202. child: const Text('重新拍照'),
  203. ),
  204. ElevatedButton(
  205. onPressed: _confirmImage,
  206. style: ElevatedButton.styleFrom(
  207. backgroundColor: Colors.green,
  208. foregroundColor: Colors.white,
  209. ),
  210. child: const Text('确认提交'),
  211. ),
  212. ],
  213. ),
  214. ],
  215. ),
  216. ),
  217. ],
  218. ),
  219. );
  220. }
  221. }