| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- // 图片解码优化工具类
- // 位置: 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<ui.Image> 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<List<ui.Image>> decodeImages({
- required List<Uint8List> bytesList,
- List<int?>? targetWidths,
- List<int?>? targetHeights,
- bool allowUpscaling = true,
- Duration timeout = const Duration(seconds: 60),
- }) async {
- try {
- final futures = <Future<ui.Image>>[];
- 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<ui.Image> 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<void> 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<ui.Image> _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<Uint8List> _readFileBytes(String filePath) async {
- final file = File(filePath);
- return await file.readAsBytes();
- }
- // ===== 使用示例 =====
- /*
- // 在 BoardPlay._init() 中使用:
- _init() async {
- Device device = context.read<Device>();
- 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);
- }
- }
- }
- */
|