image_resolution_test.dart 12.1 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
// @dart = 2.8

7
@TestOn('!chrome') // asset bundle behaves differently.
8
import 'dart:typed_data';
9
import 'dart:ui' as ui show Image;
10

11
import 'package:flutter/foundation.dart';
12 13 14
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
15
import 'package:flutter_test/flutter_test.dart';
16

17
import '../image_data.dart';
18

19 20
class TestByteData implements ByteData {
  TestByteData(this.scale);
21
  final double scale;
22 23 24

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
25 26
}

27
const String testManifest = '''
28 29
{
  "assets/image.png" : [
30
    "assets/image.png",
31 32 33 34 35 36 37 38
    "assets/1.5x/image.png",
    "assets/2.0x/image.png",
    "assets/3.0x/image.png",
    "assets/4.0x/image.png"
  ]
}
''';

39
class TestAssetBundle extends CachingAssetBundle {
40
  TestAssetBundle({ this.manifest = testManifest });
41 42 43

  final String manifest;

44
  @override
45 46
  Future<ByteData> load(String key) {
    ByteData data;
47 48
    switch (key) {
      case 'assets/image.png':
49
        data = TestByteData(1.0);
50
        break;
51
      case 'assets/1.0x/image.png':
52
        data = TestByteData(10.0); // see "...with a main asset and a 1.0x asset"
53
        break;
54
      case 'assets/1.5x/image.png':
55
        data = TestByteData(1.5);
56 57
        break;
      case 'assets/2.0x/image.png':
58
        data = TestByteData(2.0);
59 60
        break;
      case 'assets/3.0x/image.png':
61
        data = TestByteData(3.0);
62 63
        break;
      case 'assets/4.0x/image.png':
64
        data = TestByteData(4.0);
65 66
        break;
    }
67
    return SynchronousFuture<ByteData>(data);
68 69 70
  }

  @override
71
  Future<String> loadString(String key, { bool cache = true }) {
72
    if (key == 'AssetManifest.json')
73
      return SynchronousFuture<String>(manifest);
74
    return SynchronousFuture<String>(null);
75
  }
76 77

  @override
78
  String toString() => '${describeIdentity(this)}()';
79 80
}

81 82
class FakeImageStreamCompleter extends ImageStreamCompleter {
  FakeImageStreamCompleter(Future<ImageInfo> image) {
83
    image.then<void>(setImage);
84 85 86
  }
}

87
class TestAssetImage extends AssetImage {
88 89 90
  const TestAssetImage(String name, this.images) : super(name);

  final Map<double, ui.Image> images;
91

92
  @override
93
  ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
94
    ImageInfo imageInfo;
95
    key.bundle.load(key.name).then<void>((ByteData data) {
96
      final TestByteData testData = data as TestByteData;
97 98
      final ui.Image image = images[testData.scale];
      assert(image != null, 'Expected ${testData.scale} to have a key in $images');
99
      imageInfo = ImageInfo(image: image, scale: key.scale);
100
    });
101
    assert(imageInfo != null);
102 103
    return FakeImageStreamCompleter(
      SynchronousFuture<ImageInfo>(imageInfo)
104
    );
105
  }
106 107
}

108
Widget buildImageAtRatio(String imageName, Key key, double ratio, bool inferSize, Map<double, ui.Image> images, [ AssetBundle bundle ]) {
109 110 111
  const double windowSize = 500.0; // 500 logical pixels
  const double imageSize = 200.0; // 200 logical pixels

112 113
  return MediaQuery(
    data: MediaQueryData(
114 115
      size: const Size(windowSize, windowSize),
      devicePixelRatio: ratio,
116
      padding: const EdgeInsets.all(0.0),
117
    ),
118 119 120
    child: DefaultAssetBundle(
      bundle: bundle ?? TestAssetBundle(),
      child: Center(
121
        child: inferSize ?
122
          Image(
123
            key: key,
124
            excludeFromSemantics: true,
125
            image: TestAssetImage(imageName, images),
126
          ) :
127
          Image(
128
            key: key,
129
            excludeFromSemantics: true,
130
            image: TestAssetImage(imageName, images),
131 132
            height: imageSize,
            width: imageSize,
133 134 135 136
            fit: BoxFit.fill,
          ),
      ),
    ),
137 138 139
  );
}

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
Widget buildImageCacheResized(String name, Key key, int width, int height, int cacheWidth, int cacheHeight) {
  return Center(
    child: RepaintBoundary(
      child: Container(
        width: 250,
        height: 250,
        child: Center(
          child: Image.memory(
            Uint8List.fromList(kTransparentImage),
            key: key,
            excludeFromSemantics: true,
            color: const Color(0xFF00FFFF),
            colorBlendMode: BlendMode.plus,
            width: width.toDouble(),
            height: height.toDouble(),
            cacheWidth: cacheWidth,
            cacheHeight: cacheHeight,
          ),
        ),
      ),
    ),
  );
}

164
RenderImage getRenderImage(WidgetTester tester, Key key) {
165
  return tester.renderObject<RenderImage>(find.byKey(key));
166 167
}

168
Future<void> pumpTreeToLayout(WidgetTester tester, Widget widget) {
169
  const Duration pumpDuration = Duration(milliseconds: 0);
170
  const EnginePhase pumpPhase = EnginePhase.layout;
171
  return tester.pumpWidget(widget, pumpDuration, pumpPhase);
172 173 174
}

void main() {
175
  const String image = 'assets/image.png';
176

177 178 179 180 181 182 183 184
  final Map<double, ui.Image> images = <double, ui.Image>{};
  setUpAll(() async {
    for (final double scale in const <double>[0.5, 1.0, 1.5, 2.0, 4.0, 10.0]) {
      final int dimension = (48 * scale).floor();
      images[scale] = await createTestImage(width: dimension, height: dimension);
    }
  });

185
  testWidgets('Image for device pixel ratio 1.0', (WidgetTester tester) async {
186
    const double ratio = 1.0;
187
    Key key = GlobalKey();
188
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
189
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
190
    expect(getRenderImage(tester, key).scale, 1.0);
191
    key = GlobalKey();
192
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
193
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
194
    expect(getRenderImage(tester, key).scale, 1.0);
195 196
  });

197
  testWidgets('Image for device pixel ratio 0.5', (WidgetTester tester) async {
198
    const double ratio = 0.5;
199
    Key key = GlobalKey();
200
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
201
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
202
    expect(getRenderImage(tester, key).scale, 1.0);
203
    key = GlobalKey();
204
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
205
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
206
    expect(getRenderImage(tester, key).scale, 1.0);
207 208
  });

209
  testWidgets('Image for device pixel ratio 1.5', (WidgetTester tester) async {
210
    const double ratio = 1.5;
211
    Key key = GlobalKey();
212
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
213
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
214
    expect(getRenderImage(tester, key).scale, 1.5);
215
    key = GlobalKey();
216
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
217
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
218
    expect(getRenderImage(tester, key).scale, 1.5);
219 220
  });

221
  testWidgets('Image for device pixel ratio 1.75', (WidgetTester tester) async {
222
    const double ratio = 1.75;
223
    Key key = GlobalKey();
224
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
225
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
226
    expect(getRenderImage(tester, key).scale, 1.5);
227
    key = GlobalKey();
228
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
229
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
230
    expect(getRenderImage(tester, key).scale, 1.5);
231 232
  });

233
  testWidgets('Image for device pixel ratio 2.3', (WidgetTester tester) async {
234
    const double ratio = 2.3;
235
    Key key = GlobalKey();
236
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
237
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
238
    expect(getRenderImage(tester, key).scale, 2.0);
239
    key = GlobalKey();
240
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
241
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
242
    expect(getRenderImage(tester, key).scale, 2.0);
243 244
  });

245
  testWidgets('Image for device pixel ratio 3.7', (WidgetTester tester) async {
246
    const double ratio = 3.7;
247
    Key key = GlobalKey();
248
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
249
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
250
    expect(getRenderImage(tester, key).scale, 4.0);
251
    key = GlobalKey();
252
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
253
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
254
    expect(getRenderImage(tester, key).scale, 4.0);
255 256
  });

257
  testWidgets('Image for device pixel ratio 5.1', (WidgetTester tester) async {
258
    const double ratio = 5.1;
259
    Key key = GlobalKey();
260
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
261
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
262
    expect(getRenderImage(tester, key).scale, 4.0);
263
    key = GlobalKey();
264
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
265
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
266
    expect(getRenderImage(tester, key).scale, 4.0);
267 268
  });

269 270 271 272 273 274 275 276 277 278 279
  testWidgets('Image for device pixel ratio 1.0, with no main asset', (WidgetTester tester) async {
    const String manifest = '''
    {
      "assets/image.png" : [
        "assets/1.5x/image.png",
        "assets/2.0x/image.png",
        "assets/3.0x/image.png",
        "assets/4.0x/image.png"
      ]
    }
    ''';
280
    final AssetBundle bundle = TestAssetBundle(manifest: manifest);
281 282

    const double ratio = 1.0;
283
    Key key = GlobalKey();
284
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images, bundle));
285
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
286
    expect(getRenderImage(tester, key).scale, 1.5);
287
    key = GlobalKey();
288
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images, bundle));
289
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
290
    expect(getRenderImage(tester, key).scale, 1.5);
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
  });

  testWidgets('Image for device pixel ratio 1.0, with a main asset and a 1.0x asset', (WidgetTester tester) async {
    // If both a main asset and a 1.0x asset are specified, then prefer
    // the 1.0x asset.

    const String manifest = '''
    {
      "assets/image.png" : [
        "assets/image.png",
        "assets/1.0x/image.png",
        "assets/1.5x/image.png",
        "assets/2.0x/image.png",
        "assets/3.0x/image.png",
        "assets/4.0x/image.png"
      ]
    }
    ''';
309
    final AssetBundle bundle = TestAssetBundle(manifest: manifest);
310 311

    const double ratio = 1.0;
312
    Key key = GlobalKey();
313
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images, bundle));
314
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
315 316
    // Verify we got the 10x scaled image, since the TestByteData said it should be 10x.
    expect(getRenderImage(tester, key).image.height, 480);
317
    key = GlobalKey();
318
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images, bundle));
319
    expect(getRenderImage(tester, key).size, const Size(480.0, 480.0));
320 321
    // Verify we got the 10x scaled image, since the TestByteData said it should be 10x.
    expect(getRenderImage(tester, key).image.height, 480);
322 323
  });

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
  testWidgets('Image cache resize upscale display 5', (WidgetTester tester) async {
    final Key key = GlobalKey();
    await pumpTreeToLayout(tester, buildImageCacheResized(image, key, 5, 5, 20, 20));
    expect(getRenderImage(tester, key).size, const Size(5.0, 5.0));
  });

  testWidgets('Image cache resize upscale display 50', (WidgetTester tester) async {
    final Key key = GlobalKey();
    await pumpTreeToLayout(tester, buildImageCacheResized(image, key, 50, 50, 20, 20));
    expect(getRenderImage(tester, key).size, const Size(50.0, 50.0));
  });

  testWidgets('Image cache resize downscale display 5', (WidgetTester tester) async {
    final Key key = GlobalKey();
    await pumpTreeToLayout(tester, buildImageCacheResized(image, key, 5, 5, 1, 1));
    expect(getRenderImage(tester, key).size, const Size(5.0, 5.0));
  });

342
}