image.dart 3.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
// 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)) {
41
    return _cache[cacheKey]!.clone();
42 43 44 45
  }

  final ui.Image image = await _createImage(width, height);
  if (cache) {
46
    _cache[cacheKey] = image.clone();
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
  }
  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({
77 78
  required int width,
  required int height,
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
}) 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;
}