import 'dart:io'; import 'package:image/image.dart' as img; import 'package:sino_med_cloud/core/utils/logger.dart'; import 'media_exception.dart'; /// 图片处理封装(压缩 / 裁剪 / 医疗脱敏) class ImageProcessor { /// 默认压缩宽度(医疗场景推荐) static const int defaultMaxWidth = 1080; /// 默认压缩质量(85% 平衡质量和体积) static const int defaultQuality = 85; /// 压缩图片 /// /// 医疗场景:降低上传体积、去EXIF、图片标准化 /// /// - [file] 原始图片文件 /// - [maxWidth] 最大宽度,默认 1080px /// - [quality] 压缩质量(1-100),默认 85 /// /// 返回压缩后的文件(覆盖原文件) static Future compress( File file, { int maxWidth = defaultMaxWidth, int quality = defaultQuality, }) async { try { if (!await file.exists()) { throw ImageProcessingException('图片文件不存在: ${file.path}'); } final bytes = await file.readAsBytes(); if (bytes.isEmpty) { throw ImageProcessingException('图片文件为空: ${file.path}'); } final image = img.decodeImage(bytes); if (image == null) { throw ImageProcessingException('无法解析图片: ${file.path}'); } // 如果图片宽度小于等于最大宽度,只进行质量压缩 File? resultFile; if (image.width <= maxWidth) { final compressed = img.encodeJpg(image, quality: quality); resultFile = File(file.path)..writeAsBytesSync(compressed); } else { // 先缩放再压缩 final resized = img.copyResize( image, width: maxWidth, maintainAspect: true, ); final compressed = img.encodeJpg(resized, quality: quality); resultFile = File(file.path)..writeAsBytesSync(compressed); } AppLogger.d('图片压缩完成: ${file.path}, 原始大小: ${bytes.length} bytes, 压缩后: ${resultFile.lengthSync()} bytes'); return resultFile; } catch (e) { if (e is ImageProcessingException) { rethrow; } AppLogger.e('图片压缩失败: ${file.path}', e); throw ImageProcessingException( '图片压缩失败: ${e.toString()}', originalError: e, ); } } /// 获取图片信息 /// /// 返回图片的宽度、高度和文件大小 static Future> getImageInfo(File file) async { try { if (!await file.exists()) { throw ImageProcessingException('图片文件不存在: ${file.path}'); } final bytes = await file.readAsBytes(); final image = img.decodeImage(bytes); if (image == null) { throw ImageProcessingException('无法解析图片: ${file.path}'); } return { 'width': image.width, 'height': image.height, 'size': bytes.length, 'format': image.format.toString(), }; } catch (e) { if (e is ImageProcessingException) { rethrow; } AppLogger.e('获取图片信息失败: ${file.path}', e); throw ImageProcessingException( '获取图片信息失败: ${e.toString()}', originalError: e, ); } } /// 医疗脱敏处理(去除 EXIF 信息) /// /// 移除图片中的 EXIF 元数据,保护患者隐私 /// /// - [file] 原始图片文件 /// /// 返回脱敏后的文件(覆盖原文件) static Future removeExif(File file) async { try { if (!await file.exists()) { throw ImageProcessingException('图片文件不存在: ${file.path}'); } final bytes = await file.readAsBytes(); final image = img.decodeImage(bytes); if (image == null) { throw ImageProcessingException('无法解析图片: ${file.path}'); } // 重新编码图片会移除 EXIF 信息 final cleaned = img.encodeJpg(image, quality: 100); final cleanedFile = File(file.path)..writeAsBytesSync(cleaned); AppLogger.d('图片脱敏完成: ${file.path}'); return cleanedFile; } catch (e) { if (e is ImageProcessingException) { rethrow; } AppLogger.e('图片脱敏失败: ${file.path}', e); throw ImageProcessingException( '图片脱敏失败: ${e.toString()}', originalError: e, ); } } }