Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert' show base64;
3 : import 'dart:typed_data';
4 : import 'dart:ui';
5 : import 'dart:ui' as ui show decodeImageFromPixels;
6 :
7 : import 'package:flutter/foundation.dart';
8 :
9 : import '../flame.dart';
10 :
11 : class Images {
12 : final String prefix;
13 : final Map<String, _ImageAssetLoader> _loadedFiles = {};
14 :
15 28 : Images({this.prefix = 'assets/images/'});
16 :
17 0 : void clear(String fileName) {
18 0 : _loadedFiles.remove(fileName);
19 : }
20 :
21 2 : void clearCache() {
22 4 : _loadedFiles.clear();
23 : }
24 :
25 0 : Image fromCache(String fileName) {
26 0 : final image = _loadedFiles[fileName];
27 : assert(
28 0 : image?.loadedImage != null,
29 0 : 'Tried to access an inexistent entry on cache "$fileName", make sure to use the load method before accessing a file on the cache',
30 : );
31 0 : return image!.loadedImage!;
32 : }
33 :
34 0 : Future<List<Image>> loadAll(List<String> fileNames) async {
35 0 : return Future.wait(fileNames.map(load));
36 : }
37 :
38 0 : Future<Image> load(String fileName) async {
39 0 : if (!_loadedFiles.containsKey(fileName)) {
40 0 : _loadedFiles[fileName] = _ImageAssetLoader(_fetchToMemory(fileName));
41 : }
42 0 : return _loadedFiles[fileName]!.retrieve();
43 : }
44 :
45 : /// Convert an array of pixel values into an [Image] object.
46 : ///
47 : /// The [pixels] parameter is the pixel data in the encoding described by
48 : /// [PixelFormat.rgba8888], the encoding can't be changed to allow for web support.
49 : ///
50 : /// If you want the image to be decoded as it would be on the web you can set
51 : /// [runAsWeb] to `true`. Keep in mind that it is slightly slower than the native
52 : /// [ui.decodeImageFromPixels]. By default it is set to [kIsWeb].
53 5 : Future<Image> decodeImageFromPixels(
54 : Uint8List pixels,
55 : int width,
56 : int height, {
57 : bool runAsWeb = kIsWeb,
58 : }) {
59 5 : final completer = Completer<Image>();
60 : if (runAsWeb) {
61 2 : completer.complete(_createBmp(pixels, width, height));
62 : } else {
63 5 : ui.decodeImageFromPixels(
64 : pixels,
65 : width,
66 : height,
67 : PixelFormat.rgba8888,
68 5 : completer.complete,
69 : );
70 : }
71 :
72 5 : return completer.future;
73 : }
74 :
75 0 : Future<Image> fromBase64(String fileName, String base64) async {
76 0 : if (!_loadedFiles.containsKey(fileName)) {
77 0 : _loadedFiles[fileName] = _ImageAssetLoader(_fetchFromBase64(base64));
78 : }
79 0 : return _loadedFiles[fileName]!.retrieve();
80 : }
81 :
82 0 : Future<Image> _fetchFromBase64(String base64Data) async {
83 0 : final data = base64Data.substring(base64Data.indexOf(',') + 1);
84 0 : final bytes = base64.decode(data);
85 0 : return _loadBytes(bytes);
86 : }
87 :
88 0 : Future<Image> _fetchToMemory(String name) async {
89 0 : final data = await Flame.bundle.load('$prefix$name');
90 0 : final bytes = Uint8List.view(data.buffer);
91 0 : return _loadBytes(bytes);
92 : }
93 :
94 0 : Future<Image> _loadBytes(Uint8List bytes) {
95 0 : final completer = Completer<Image>();
96 0 : decodeImageFromList(bytes, completer.complete);
97 0 : return completer.future;
98 : }
99 :
100 1 : Future<Image> _createBmp(Uint8List pixels, int width, int height) async {
101 3 : final size = (width * height * 4) + 122;
102 1 : final bmp = Uint8List(size);
103 2 : bmp.buffer.asByteData()
104 1 : ..setUint8(0x0, 0x42)
105 1 : ..setUint8(0x1, 0x4d)
106 1 : ..setInt32(0x2, size, Endian.little)
107 1 : ..setInt32(0xa, 122, Endian.little)
108 1 : ..setUint32(0xe, 108, Endian.little)
109 1 : ..setUint32(0x12, width, Endian.little)
110 2 : ..setUint32(0x16, -height, Endian.little)
111 1 : ..setUint16(0x1a, 1, Endian.little)
112 1 : ..setUint32(0x1c, 32, Endian.little)
113 1 : ..setUint32(0x1e, 3, Endian.little)
114 3 : ..setUint32(0x22, width * height * 4, Endian.little)
115 1 : ..setUint32(0x36, 0x000000ff, Endian.little)
116 1 : ..setUint32(0x3a, 0x0000ff00, Endian.little)
117 1 : ..setUint32(0x3e, 0x00ff0000, Endian.little)
118 1 : ..setUint32(0x42, 0xff000000, Endian.little);
119 :
120 1 : bmp.setRange(122, size, pixels);
121 :
122 2 : final codec = await instantiateImageCodec(bmp);
123 2 : final frame = await codec.getNextFrame();
124 :
125 1 : return frame.image;
126 : }
127 : }
128 :
129 : class _ImageAssetLoader {
130 0 : _ImageAssetLoader(this.future);
131 :
132 : Image? loadedImage;
133 : Future<Image> future;
134 :
135 0 : Future<Image> retrieve() async {
136 0 : return loadedImage ??= await future;
137 : }
138 : }
|