
* Dispose of images after using them Adds ImageInfo.clone, ImageInfo.dispose, and ImageInfo.isCloneOf. Makes all stateful holders of ui.Image objects properly clone and dispose of them. Behavior change for ImageStreamCompleter: Removing the last listener after registering at least one listener results in the object being disposed, and new listeners cannot be added, unless a keepAlive handle is created. Makes all non-stateful holders of ui.Image (i.e. paintImage and RawImage) assert that whoever gave the image to them has not disposed it on them.
120 lines
3.9 KiB
Dart
120 lines
3.9 KiB
Dart
// 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 await _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 await 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;
|
|
}
|