debug.dart 7.62 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5

6
import 'dart:io';
7
import 'dart:ui' show Size, hashValues;
8

9 10 11 12 13 14 15 16 17
import 'package:flutter/foundation.dart';

/// Whether to replace all shadows with solid color blocks.
///
/// This is useful when writing golden file tests (see [matchesGoldenFile]) since
/// the rendering of shadows is not guaranteed to be pixel-for-pixel identical from
/// version to version (or even from run to run).
bool debugDisableShadows = false;

18 19 20 21 22 23 24 25 26 27 28 29 30 31
/// Signature for a method that returns an [HttpClient].
///
/// Used by [debugNetworkImageHttpClientProvider].
typedef HttpClientProvider = HttpClient Function();

/// Provider from which [NetworkImage] will get its [HttpClient] in debug builds.
///
/// If this value is unset, [NetworkImage] will use its own internally-managed
/// [HttpClient].
///
/// This setting can be overridden for testing to ensure that each test receives
/// a mock client that hasn't been affected by other tests.
///
/// This value is ignored in non-debug builds.
32
HttpClientProvider? debugNetworkImageHttpClientProvider;
33

34 35 36 37 38
/// Called when the framework is about to paint an [Image] to a [Canvas] with an
/// [ImageSizeInfo] that contains the decoded size of the image as well as its
/// output size.
///
/// See: [debugOnPaintImage].
39 40
typedef PaintImageCallback = void Function(ImageSizeInfo);

41 42
/// Tracks the bytes used by a [dart:ui.Image] compared to the bytes needed to
/// paint that image without scaling it.
43 44
@immutable
class ImageSizeInfo {
45 46
  /// Creates an object to track the backing size of a [dart:ui.Image] compared
  /// to its display size on a [Canvas].
47 48 49 50
  ///
  /// This class is used by the framework when it paints an image to a canvas
  /// to report to `dart:developer`'s [postEvent], as well as to the
  /// [debugOnPaintImage] callback if it is set.
51
  const ImageSizeInfo({this.source, this.displaySize, required this.imageSize});
52 53 54

  /// A unique identifier for this image, for example its asset path or network
  /// URL.
55
  final String? source;
56 57

  /// The size of the area the image will be rendered in.
58
  final Size? displaySize;
59 60 61 62 63

  /// The size the image has been decoded to.
  final Size imageSize;

  /// The number of bytes needed to render the image without scaling it.
64
  int get displaySizeInBytes => _sizeToBytes(displaySize!);
65 66 67 68 69 70 71 72 73 74 75

  /// The number of bytes used by the image in memory.
  int get decodedSizeInBytes => _sizeToBytes(imageSize);

  int _sizeToBytes(Size size) {
    // Assume 4 bytes per pixel and that mipmapping will be used, which adds
    // 4/3.
    return (size.width * size.height * 4 * (4/3)).toInt();
  }

  /// Returns a JSON encodable representation of this object.
76 77
  Map<String, Object?> toJson() {
    return <String, Object?>{
78
      'source': source,
79 80 81 82 83 84
      if (displaySize != null)
        'displaySize': <String, Object?>{
          'width': displaySize!.width,
          'height': displaySize!.height,
        },
      'imageSize': <String, Object?>{
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 120 121 122 123 124 125 126 127 128 129 130 131 132
        'width': imageSize.width,
        'height': imageSize.height,
      },
      'displaySizeInBytes': displaySizeInBytes,
      'decodedSizeInBytes': decodedSizeInBytes,
    };
  }

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is ImageSizeInfo
        && other.source == source
        && other.imageSize == imageSize
        && other.displaySize == displaySize;
  }

  @override
  int get hashCode => hashValues(source, displaySize, imageSize);

  @override
  String toString() => 'ImageSizeInfo($source, imageSize: $imageSize, displaySize: $displaySize)';
}

/// If not null, called when the framework is about to paint an [Image] to a
/// [Canvas] with an [ImageSizeInfo] that contains the decoded size of the
/// image as well as its output size.
///
/// A test can use this callback to detect if images under test are being
/// rendered with the appropriate cache dimensions.
///
/// For example, if a 100x100 image is decoded it takes roughly 53kb in memory
/// (including mipmapping overhead). If it is only ever displayed at 50x50, it
/// would take only 13kb if the cacheHeight/cacheWidth parameters had been
/// specified at that size. This problem becomes more serious for larger
/// images, such as a high resolution image from a 12MP camera, which would be
/// 64mb when decoded.
///
/// When using this callback, developers should consider whether the image will
/// be panned or scaled up in the application, how many images are being
/// displayed, and whether the application will run on multiple devices with
/// different resolutions and memory capacities. For example, it should be fine
/// to have an image that animates from thumbnail size to full screen be at
/// a higher resolution while animating, but it would be problematic to have
/// a grid or list of such thumbnails all be at the full resolution at the same
/// time.
133
PaintImageCallback? debugOnPaintImage;
134

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
/// If true, the framework will color invert and horizontally flip images that
/// have been decoded to a size taking at least [debugImageOverheadAllowance]
/// bytes more than necessary.
///
/// It will also call [FlutterError.reportError] with information about the
/// image's decoded size and its display size, which can be used resize the
/// asset before shipping it, apply `cacheHeight` or `cacheWidth` parameters, or
/// directly use a [ResizeImage]. Whenever possible, resizing the image asset
/// itself should be preferred, to avoid unnecessary network traffic, disk space
/// usage, and other memory overhead incurred during decoding.
///
/// Developers using this flag should test their application on appropriate
/// devices and display sizes for their expected deployment targets when using
/// these parameters. For example, an application that responsively resizes
/// images for a desktop and mobile layout should avoid decoding all images at
/// sizes appropriate for mobile when on desktop. Applications should also avoid
/// animating these parameters, as each change will result in a newly decoded
/// image. For example, an image that always grows into view should decode only
/// at its largest size, whereas an image that normally is a thumbnail and then
/// pops into view should be decoded at its smallest size for the thumbnail and
/// the largest size when needed.
///
/// This has no effect unless asserts are enabled.
bool debugInvertOversizedImages = false;

160 161
const int _imageOverheadAllowanceDefault = 128 * 1024;

162 163 164
/// The number of bytes an image must use before it triggers inversion when
/// [debugInvertOversizedImages] is true.
///
165 166
/// Default is 128kb.
int debugImageOverheadAllowance = _imageOverheadAllowanceDefault;
167

168 169 170 171 172
/// Returns true if none of the painting library debug variables have been changed.
///
/// This function is used by the test framework to ensure that debug variables
/// haven't been inadvertently changed.
///
173 174
/// See [the painting library](painting/painting-library.html) for a complete
/// list.
175 176 177 178
///
/// The `debugDisableShadowsOverride` argument can be provided to override
/// the expected value for [debugDisableShadows]. (This exists because the
/// test framework itself overrides this value in some cases.)
179
bool debugAssertAllPaintingVarsUnset(String reason, { bool debugDisableShadowsOverride = false }) {
180
  assert(() {
181
    if (debugDisableShadows != debugDisableShadowsOverride ||
182
        debugNetworkImageHttpClientProvider != null ||
183 184
        debugOnPaintImage != null ||
        debugInvertOversizedImages == true ||
185
        debugImageOverheadAllowance != _imageOverheadAllowanceDefault) {
186
      throw FlutterError(reason);
187 188 189 190 191
    }
    return true;
  }());
  return true;
}