image_decoder.dart 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // 图片解码优化工具类
  2. // 位置: lib/utils/image_decoder.dart
  3. //
  4. // 主要优化:
  5. // 1. 使用 compute 在 isolate 中解码,避免阻塞主线程
  6. // 2. 添加解码超时保护
  7. // 3. 支持批量解码
  8. // 4. 内存优化:解码后立即释放原始数据
  9. import 'dart:async';
  10. import 'dart:io';
  11. import 'dart:ui' as ui;
  12. import 'package:flutter/foundation.dart';
  13. import 'package:logging/logging.dart';
  14. final Logger _log = Logger('image_decoder.dart');
  15. class ImageDecoder {
  16. // ✅ 在 isolate 中解码单张图片
  17. static Future<ui.Image> decodeImage({
  18. required Uint8List bytes,
  19. int? targetWidth,
  20. int? targetHeight,
  21. bool allowUpscaling = true,
  22. Duration timeout = const Duration(seconds: 30),
  23. }) async {
  24. try {
  25. // 使用 compute 在独立 isolate 中执行解码
  26. final image = await compute(
  27. _decodeImageInIsolate,
  28. _DecodeParams(bytes: bytes, targetWidth: targetWidth, targetHeight: targetHeight, allowUpscaling: allowUpscaling),
  29. ).timeout(timeout);
  30. _log.info('Image decoded successfully: ${image.width}x${image.height}');
  31. return image;
  32. } on TimeoutException {
  33. _log.severe('Image decode timeout after ${timeout.inSeconds}s');
  34. throw Exception('Image decode timeout');
  35. } catch (e, stack) {
  36. _log.severe('Image decode failed', e, stack);
  37. rethrow;
  38. }
  39. }
  40. // ✅ 批量解码多张图片(并行)
  41. static Future<List<ui.Image>> decodeImages({
  42. required List<Uint8List> bytesList,
  43. List<int?>? targetWidths,
  44. List<int?>? targetHeights,
  45. bool allowUpscaling = true,
  46. Duration timeout = const Duration(seconds: 60),
  47. }) async {
  48. try {
  49. final futures = <Future<ui.Image>>[];
  50. for (int i = 0; i < bytesList.length; i++) {
  51. futures.add(
  52. decodeImage(bytes: bytesList[i], targetWidth: targetWidths?[i], targetHeight: targetHeights?[i], allowUpscaling: allowUpscaling, timeout: timeout),
  53. );
  54. }
  55. return await Future.wait(futures);
  56. } catch (e, stack) {
  57. _log.severe('Batch image decode failed', e, stack);
  58. rethrow;
  59. }
  60. }
  61. // ✅ 从文件路径解码(自动读取文件)
  62. static Future<ui.Image> decodeImageFromFile({required String filePath, int? targetWidth, int? targetHeight, bool allowUpscaling = true}) async {
  63. try {
  64. final bytes = await compute(_readFileBytes, filePath);
  65. return await decodeImage(bytes: bytes, targetWidth: targetWidth, targetHeight: targetHeight, allowUpscaling: allowUpscaling);
  66. } catch (e, stack) {
  67. _log.severe('Failed to decode image from file: $filePath', e, stack);
  68. rethrow;
  69. }
  70. }
  71. // ✅ 预解码(用于预加载,不返回结果)
  72. static Future<void> preDecodeImage({required Uint8List bytes, int? targetWidth, int? targetHeight}) async {
  73. try {
  74. final image = await decodeImage(bytes: bytes, targetWidth: targetWidth, targetHeight: targetHeight);
  75. // 立即释放,只是为了触发解码缓存
  76. image.dispose();
  77. } catch (e) {
  78. _log.warning('Pre-decode failed (non-critical): $e');
  79. }
  80. }
  81. }
  82. // ===== Isolate 函数(必须是顶层函数或静态函数)=====
  83. // 解码参数类
  84. class _DecodeParams {
  85. final Uint8List bytes;
  86. final int? targetWidth;
  87. final int? targetHeight;
  88. final bool allowUpscaling;
  89. _DecodeParams({required this.bytes, this.targetWidth, this.targetHeight, this.allowUpscaling = true});
  90. }
  91. // ✅ 在 isolate 中执行的解码函数
  92. Future<ui.Image> _decodeImageInIsolate(_DecodeParams params) async {
  93. final codec = await ui.instantiateImageCodec(
  94. params.bytes,
  95. targetWidth: params.targetWidth,
  96. targetHeight: params.targetHeight,
  97. allowUpscaling: params.allowUpscaling,
  98. );
  99. final frameInfo = await codec.getNextFrame();
  100. return frameInfo.image;
  101. }
  102. // ✅ 在 isolate 中读取文件
  103. Future<Uint8List> _readFileBytes(String filePath) async {
  104. final file = File(filePath);
  105. return await file.readAsBytes();
  106. }
  107. // ===== 使用示例 =====
  108. /*
  109. // 在 BoardPlay._init() 中使用:
  110. _init() async {
  111. Device device = context.read<Device>();
  112. setState(() {
  113. _isLoading = true;
  114. });
  115. try {
  116. final dpr = device.effectivePixelRatio;
  117. final targetRect = device.targetRect;
  118. final bestImageSize = device.bestImageSize;
  119. // ✅ 并行解码主图和卡牌图
  120. final imageBytes = await itemLoader.ensureDataLoaded();
  121. final cardBytes = await rootBundle.load(
  122. widget.item.hard
  123. ? 'assets/images/backcard_red.png'
  124. : 'assets/images/backcard_blue.png'
  125. );
  126. final Size bestCardImageSize = Size(
  127. targetRect.width * dpr / widget.item.rows,
  128. targetRect.height * dpr / widget.item.cols,
  129. );
  130. // ✅ 使用优化的解码器
  131. final images = await ImageDecoder.decodeImages(
  132. bytesList: [
  133. imageBytes,
  134. cardBytes.buffer.asUint8List(),
  135. ],
  136. targetWidths: [
  137. bestImageSize.width.round(),
  138. bestCardImageSize.width.round(),
  139. ],
  140. targetHeights: [
  141. bestImageSize.height.round(),
  142. bestCardImageSize.height.round(),
  143. ],
  144. );
  145. final image = images[0];
  146. final cardImage = images[1];
  147. _log.info('Images decoded: main=${image.width}x${image.height}, card=${cardImage.width}x${cardImage.height}');
  148. // 3. 构建或恢复 Board 实例
  149. if (widget.reset) {
  150. board = await Board.create(
  151. this, image, cardImage,
  152. widget.item.rows, widget.item.cols,
  153. widget.item.hard, targetRect, device
  154. );
  155. } else {
  156. final jsonFile = await localFile(widget.item.jsonPath);
  157. if (await jsonFile.exists()) {
  158. showDealing = false;
  159. board = await Board.restore(
  160. this, image, cardImage,
  161. widget.item.rows, widget.item.cols,
  162. widget.item.hard, targetRect, device,
  163. widget.item.jsonPath
  164. );
  165. } else {
  166. board = await Board.create(
  167. this, image, cardImage,
  168. widget.item.rows, widget.item.cols,
  169. widget.item.hard, targetRect, device
  170. );
  171. _reportLevelStart();
  172. }
  173. }
  174. // 4. 初始化准备
  175. board!.prepare();
  176. _loadFingerImageAndSetupHint();
  177. if (mounted) {
  178. setState(() {
  179. _isLoading = false;
  180. });
  181. }
  182. // 5. 启动入场动画
  183. if (showDealing) {
  184. _prepareAnimationController.forward(from: 0.0);
  185. } else {
  186. board!.start();
  187. }
  188. } catch (error, stack) {
  189. _log.severe('Board _init critical error', error, stack);
  190. if (mounted) {
  191. Fluttertoast.showToast(
  192. msg: AppLocalizations.of(context)!.networkNotGood,
  193. toastLength: Toast.LENGTH_LONG,
  194. gravity: ToastGravity.CENTER,
  195. backgroundColor: SkinHelper.slotBorderColor,
  196. textColor: Colors.white,
  197. );
  198. Navigator.pop(context);
  199. }
  200. }
  201. }
  202. */