image_resolution_test.dart 11.8 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
@TestOn('!chrome') // asset bundle behaves differently.
6
import 'dart:async';
7
import 'dart:typed_data';
8
import 'dart:ui' as ui show Image, ImageByteFormat;
9

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

16 17
import 'image_data.dart';

18
class TestImage implements ui.Image {
19 20
  TestImage(this.scale);
  final double scale;
21 22

  @override
23
  int get width => (48*scale).floor();
24 25

  @override
26
  int get height => (48*scale).floor();
27 28

  @override
29
  void dispose() { }
30 31

  @override
32
  Future<ByteData> toByteData({ ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba }) async {
33
    throw UnsupportedError('Cannot encode test image');
34
  }
35 36
}

37 38
class TestByteData implements ByteData {
  TestByteData(this.scale);
39
  final double scale;
40 41 42

  @override
  dynamic noSuchMethod(Invocation invocation) => null;
43 44
}

45
const String testManifest = '''
46 47
{
  "assets/image.png" : [
48
    "assets/image.png",
49 50 51 52 53 54 55 56
    "assets/1.5x/image.png",
    "assets/2.0x/image.png",
    "assets/3.0x/image.png",
    "assets/4.0x/image.png"
  ]
}
''';

57
class TestAssetBundle extends CachingAssetBundle {
58
  TestAssetBundle({ this.manifest = testManifest });
59 60 61

  final String manifest;

62
  @override
63 64
  Future<ByteData> load(String key) {
    ByteData data;
65 66
    switch (key) {
      case 'assets/image.png':
67
        data = TestByteData(1.0);
68
        break;
69
      case 'assets/1.0x/image.png':
70
        data = TestByteData(10.0); // see "...with a main asset and a 1.0x asset"
71
        break;
72
      case 'assets/1.5x/image.png':
73
        data = TestByteData(1.5);
74 75
        break;
      case 'assets/2.0x/image.png':
76
        data = TestByteData(2.0);
77 78
        break;
      case 'assets/3.0x/image.png':
79
        data = TestByteData(3.0);
80 81
        break;
      case 'assets/4.0x/image.png':
82
        data = TestByteData(4.0);
83 84
        break;
    }
85
    return SynchronousFuture<ByteData>(data);
86 87 88
  }

  @override
89
  Future<String> loadString(String key, { bool cache = true }) {
90
    if (key == 'AssetManifest.json')
91
      return SynchronousFuture<String>(manifest);
92
    return SynchronousFuture<String>(null);
93
  }
94 95

  @override
96
  String toString() => '${describeIdentity(this)}()';
97 98
}

99 100
class FakeImageStreamCompleter extends ImageStreamCompleter {
  FakeImageStreamCompleter(Future<ImageInfo> image) {
101
    image.then<void>(setImage);
102 103 104
  }
}

105
class TestAssetImage extends AssetImage {
106
  const TestAssetImage(String name) : super(name);
107

108
  @override
109
  ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
110
    ImageInfo imageInfo;
111
    key.bundle.load(key.name).then<void>((ByteData data) {
112
      final TestByteData testData = data as TestByteData;
113 114
      final ui.Image image = TestImage(testData.scale);
      imageInfo = ImageInfo(image: image, scale: key.scale);
115
    });
116
    assert(imageInfo != null);
117 118
    return FakeImageStreamCompleter(
      SynchronousFuture<ImageInfo>(imageInfo)
119
    );
120
  }
121 122
}

123
Widget buildImageAtRatio(String image, Key key, double ratio, bool inferSize, [ AssetBundle bundle ]) {
124 125 126
  const double windowSize = 500.0; // 500 logical pixels
  const double imageSize = 200.0; // 200 logical pixels

127 128
  return MediaQuery(
    data: MediaQueryData(
129 130
      size: const Size(windowSize, windowSize),
      devicePixelRatio: ratio,
131
      padding: const EdgeInsets.all(0.0),
132
    ),
133 134 135
    child: DefaultAssetBundle(
      bundle: bundle ?? TestAssetBundle(),
      child: Center(
136
        child: inferSize ?
137
          Image(
138
            key: key,
139
            excludeFromSemantics: true,
140
            image: TestAssetImage(image),
141
          ) :
142
          Image(
143
            key: key,
144
            excludeFromSemantics: true,
145
            image: TestAssetImage(image),
146 147
            height: imageSize,
            width: imageSize,
148 149 150 151
            fit: BoxFit.fill,
          ),
      ),
    ),
152 153 154
  );
}

155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
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,
          ),
        ),
      ),
    ),
  );
}

179
RenderImage getRenderImage(WidgetTester tester, Key key) {
180
  return tester.renderObject<RenderImage>(find.byKey(key));
181
}
182
TestImage getTestImage(WidgetTester tester, Key key) {
183
  return tester.renderObject<RenderImage>(find.byKey(key)).image as TestImage;
184 185
}

186
Future<void> pumpTreeToLayout(WidgetTester tester, Widget widget) {
187
  const Duration pumpDuration = Duration(milliseconds: 0);
188
  const EnginePhase pumpPhase = EnginePhase.layout;
189
  return tester.pumpWidget(widget, pumpDuration, pumpPhase);
190 191 192
}

void main() {
193
  const String image = 'assets/image.png';
194

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

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

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

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

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

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

267
  testWidgets('Image for device pixel ratio 5.1', (WidgetTester tester) async {
268
    const double ratio = 5.1;
269
    Key key = GlobalKey();
270
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false));
271 272
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
    expect(getTestImage(tester, key).scale, 4.0);
273
    key = GlobalKey();
274
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true));
275 276
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
    expect(getTestImage(tester, key).scale, 4.0);
277 278
  });

279 280 281 282 283 284 285 286 287 288 289
  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"
      ]
    }
    ''';
290
    final AssetBundle bundle = TestAssetBundle(manifest: manifest);
291 292

    const double ratio = 1.0;
293
    Key key = GlobalKey();
294 295 296
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, bundle));
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
    expect(getTestImage(tester, key).scale, 1.5);
297
    key = GlobalKey();
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, bundle));
    expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
    expect(getTestImage(tester, key).scale, 1.5);
  });

  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"
      ]
    }
    ''';
319
    final AssetBundle bundle = TestAssetBundle(manifest: manifest);
320 321

    const double ratio = 1.0;
322
    Key key = GlobalKey();
323 324 325
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, bundle));
    expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
    expect(getTestImage(tester, key).scale, 10.0);
326
    key = GlobalKey();
327 328 329 330 331
    await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, bundle));
    expect(getRenderImage(tester, key).size, const Size(480.0, 480.0));
    expect(getTestImage(tester, key).scale, 10.0);
  });

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
  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));
  });

350
}