// 图片解码优化工具类 // 位置: lib/utils/image_decoder.dart // // 主要优化: // 1. 使用 compute 在 isolate 中解码,避免阻塞主线程 // 2. 添加解码超时保护 // 3. 支持批量解码 // 4. 内存优化:解码后立即释放原始数据 import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; final Logger _log = Logger('image_decoder.dart'); class ImageDecoder { // ✅ 在 isolate 中解码单张图片 static Future decodeImage({ required Uint8List bytes, int? targetWidth, int? targetHeight, bool allowUpscaling = true, Duration timeout = const Duration(seconds: 30), }) async { try { // 使用 compute 在独立 isolate 中执行解码 final image = await compute( _decodeImageInIsolate, _DecodeParams(bytes: bytes, targetWidth: targetWidth, targetHeight: targetHeight, allowUpscaling: allowUpscaling), ).timeout(timeout); _log.info('Image decoded successfully: ${image.width}x${image.height}'); return image; } on TimeoutException { _log.severe('Image decode timeout after ${timeout.inSeconds}s'); throw Exception('Image decode timeout'); } catch (e, stack) { _log.severe('Image decode failed', e, stack); rethrow; } } // ✅ 批量解码多张图片(并行) static Future> decodeImages({ required List bytesList, List? targetWidths, List? targetHeights, bool allowUpscaling = true, Duration timeout = const Duration(seconds: 60), }) async { try { final futures = >[]; for (int i = 0; i < bytesList.length; i++) { futures.add( decodeImage(bytes: bytesList[i], targetWidth: targetWidths?[i], targetHeight: targetHeights?[i], allowUpscaling: allowUpscaling, timeout: timeout), ); } return await Future.wait(futures); } catch (e, stack) { _log.severe('Batch image decode failed', e, stack); rethrow; } } // ✅ 从文件路径解码(自动读取文件) static Future decodeImageFromFile({required String filePath, int? targetWidth, int? targetHeight, bool allowUpscaling = true}) async { try { final bytes = await compute(_readFileBytes, filePath); return await decodeImage(bytes: bytes, targetWidth: targetWidth, targetHeight: targetHeight, allowUpscaling: allowUpscaling); } catch (e, stack) { _log.severe('Failed to decode image from file: $filePath', e, stack); rethrow; } } // ✅ 预解码(用于预加载,不返回结果) static Future preDecodeImage({required Uint8List bytes, int? targetWidth, int? targetHeight}) async { try { final image = await decodeImage(bytes: bytes, targetWidth: targetWidth, targetHeight: targetHeight); // 立即释放,只是为了触发解码缓存 image.dispose(); } catch (e) { _log.warning('Pre-decode failed (non-critical): $e'); } } } // ===== Isolate 函数(必须是顶层函数或静态函数)===== // 解码参数类 class _DecodeParams { final Uint8List bytes; final int? targetWidth; final int? targetHeight; final bool allowUpscaling; _DecodeParams({required this.bytes, this.targetWidth, this.targetHeight, this.allowUpscaling = true}); } // ✅ 在 isolate 中执行的解码函数 Future _decodeImageInIsolate(_DecodeParams params) async { final codec = await ui.instantiateImageCodec( params.bytes, targetWidth: params.targetWidth, targetHeight: params.targetHeight, allowUpscaling: params.allowUpscaling, ); final frameInfo = await codec.getNextFrame(); return frameInfo.image; } // ✅ 在 isolate 中读取文件 Future _readFileBytes(String filePath) async { final file = File(filePath); return await file.readAsBytes(); } // ===== 使用示例 ===== /* // 在 BoardPlay._init() 中使用: _init() async { Device device = context.read(); setState(() { _isLoading = true; }); try { final dpr = device.effectivePixelRatio; final targetRect = device.targetRect; final bestImageSize = device.bestImageSize; // ✅ 并行解码主图和卡牌图 final imageBytes = await itemLoader.ensureDataLoaded(); final cardBytes = await rootBundle.load( widget.item.hard ? 'assets/images/backcard_red.png' : 'assets/images/backcard_blue.png' ); final Size bestCardImageSize = Size( targetRect.width * dpr / widget.item.rows, targetRect.height * dpr / widget.item.cols, ); // ✅ 使用优化的解码器 final images = await ImageDecoder.decodeImages( bytesList: [ imageBytes, cardBytes.buffer.asUint8List(), ], targetWidths: [ bestImageSize.width.round(), bestCardImageSize.width.round(), ], targetHeights: [ bestImageSize.height.round(), bestCardImageSize.height.round(), ], ); final image = images[0]; final cardImage = images[1]; _log.info('Images decoded: main=${image.width}x${image.height}, card=${cardImage.width}x${cardImage.height}'); // 3. 构建或恢复 Board 实例 if (widget.reset) { board = await Board.create( this, image, cardImage, widget.item.rows, widget.item.cols, widget.item.hard, targetRect, device ); } else { final jsonFile = await localFile(widget.item.jsonPath); if (await jsonFile.exists()) { showDealing = false; board = await Board.restore( this, image, cardImage, widget.item.rows, widget.item.cols, widget.item.hard, targetRect, device, widget.item.jsonPath ); } else { board = await Board.create( this, image, cardImage, widget.item.rows, widget.item.cols, widget.item.hard, targetRect, device ); _reportLevelStart(); } } // 4. 初始化准备 board!.prepare(); _loadFingerImageAndSetupHint(); if (mounted) { setState(() { _isLoading = false; }); } // 5. 启动入场动画 if (showDealing) { _prepareAnimationController.forward(from: 0.0); } else { board!.start(); } } catch (error, stack) { _log.severe('Board _init critical error', error, stack); if (mounted) { Fluttertoast.showToast( msg: AppLocalizations.of(context)!.networkNotGood, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.CENTER, backgroundColor: SkinHelper.slotBorderColor, textColor: Colors.white, ); Navigator.pop(context); } } } */