| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/painting.dart'; |
| |
| import 'test_async_utils.dart'; |
| |
| final Map<int, ui.Image> _cache = <int, ui.Image>{}; |
| |
| /// Creates an arbitrarily sized image for testing. |
| /// |
| /// If the [cache] parameter is set to true, the image will be cached for the |
| /// rest of this suite. This is normally desirable, assuming a test suite uses |
| /// images with the same dimensions in most tests, as it will save on memory |
| /// usage and CPU time over the course of the suite. However, it should be |
| /// avoided for images that are used only once in a test suite, especially if |
| /// the image is large, as it will require holding on to the memory for that |
| /// image for the duration of the suite. |
| /// |
| /// This method requires real async work, and will not work properly in the |
| /// [FakeAsync] zones set up by [testWidgets]. Typically, it should be invoked |
| /// as a setup step before [testWidgets] are run, such as [setUp] or [setUpAll]. |
| /// If needed, it can be invoked using [WidgetTester.runAsync]. |
| Future<ui.Image> createTestImage({ |
| int width = 1, |
| int height = 1, |
| bool cache = true, |
| }) => TestAsyncUtils.guard(() async { |
| assert(width != null && width > 0); |
| assert(height != null && height > 0); |
| assert(cache != null); |
| |
| final int cacheKey = hashValues(width, height); |
| if (cache && _cache.containsKey(cacheKey)) { |
| return _cache[cacheKey]!.clone(); |
| } |
| |
| final ui.Image image = await _createImage(width, height); |
| if (cache) { |
| _cache[cacheKey] = image.clone(); |
| } |
| return image; |
| }); |
| |
| Future<ui.Image> _createImage(int width, int height) async { |
| if (kIsWeb) { |
| return _webCreateTestImage( |
| width: width, |
| height: height, |
| ); |
| } |
| |
| final Completer<ui.Image> completer = Completer<ui.Image>(); |
| ui.decodeImageFromPixels( |
| Uint8List.fromList(List<int>.filled(width * height * 4, 0, growable: false)), |
| width, |
| height, |
| ui.PixelFormat.rgba8888, |
| (ui.Image image) { |
| completer.complete(image); |
| }, |
| ); |
| return completer.future; |
| } |
| |
| /// Web doesn't support [decodeImageFromPixels]. Instead, generate a 1bpp BMP |
| /// and just use [instantiateImageCodec]. |
| // TODO(dnfield): Remove this when https://github.com/flutter/flutter/issues/49244 |
| // is resolved. |
| Future<ui.Image> _webCreateTestImage({ |
| required int width, |
| required int height, |
| }) async { |
| // See https://en.wikipedia.org/wiki/BMP_file_format for format examples. |
| final int bufferSize = 0x36 + (width * height); |
| final ByteData bmpData = ByteData(bufferSize); |
| // 'BM' header |
| bmpData.setUint8(0x00, 0x42); |
| bmpData.setUint8(0x01, 0x4D); |
| // Size of data |
| bmpData.setUint32(0x02, bufferSize, Endian.little); |
| // Offset where pixel array begins |
| bmpData.setUint32(0x0A, 0x36, Endian.little); |
| // Bytes in DIB header |
| bmpData.setUint32(0x0E, 0x28, Endian.little); |
| // width |
| bmpData.setUint32(0x12, width, Endian.little); |
| // height |
| bmpData.setUint32(0x16, height, Endian.little); |
| // Color panes |
| bmpData.setUint16(0x1A, 0x01, Endian.little); |
| // bpp |
| bmpData.setUint16(0x1C, 0x01, Endian.little); |
| // no compression |
| bmpData.setUint32(0x1E, 0x00, Endian.little); |
| // raw bitmap data size |
| bmpData.setUint32(0x22, width * height, Endian.little); |
| // print DPI width |
| bmpData.setUint32(0x26, width, Endian.little); |
| // print DPI height |
| bmpData.setUint32(0x2A, height, Endian.little); |
| // colors in the palette |
| bmpData.setUint32(0x2E, 0x00, Endian.little); |
| // important colors |
| bmpData.setUint32(0x32, 0x00, Endian.little); |
| // rest of data is zeroed as black pixels. |
| |
| final ui.Codec codec = await ui.instantiateImageCodec( |
| bmpData.buffer.asUint8List(), |
| ); |
| final ui.FrameInfo frameInfo = await codec.getNextFrame(); |
| return frameInfo.image; |
| } |