// 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; }