diff --git a/packages/flutter/lib/src/services/image_resolution.dart b/packages/flutter/lib/src/services/image_resolution.dart index 22cb6cf0a30..78764c3eccd 100644 --- a/packages/flutter/lib/src/services/image_resolution.dart +++ b/packages/flutter/lib/src/services/image_resolution.dart @@ -150,7 +150,6 @@ class AssetImage extends AssetBundleImageProvider { final SplayTreeMap mapping = new SplayTreeMap(); for (String candidate in candidates) mapping[_parseScale(candidate)] = candidate; - mapping[_naturalResolution] = main; // TODO(ianh): implement support for config.locale, config.size, config.platform // (then document this over in the Image.asset docs) return _findNearest(mapping, config.devicePixelRatio); diff --git a/packages/flutter/test/widgets/image_resolution_test.dart b/packages/flutter/test/widgets/image_resolution_test.dart index 1048f85c091..02f9ced8024 100644 --- a/packages/flutter/test/widgets/image_resolution_test.dart +++ b/packages/flutter/test/widgets/image_resolution_test.dart @@ -34,9 +34,10 @@ class TestByteData implements ByteData { dynamic noSuchMethod(Invocation invocation) => null; } -String testManifest = ''' +const String testManifest = ''' { "assets/image.png" : [ + "assets/image.png", "assets/1.5x/image.png", "assets/2.0x/image.png", "assets/3.0x/image.png", @@ -46,6 +47,10 @@ String testManifest = ''' '''; class TestAssetBundle extends CachingAssetBundle { + TestAssetBundle({ this.manifest: testManifest }); + + final String manifest; + @override Future load(String key) { ByteData data; @@ -53,6 +58,9 @@ class TestAssetBundle extends CachingAssetBundle { case 'assets/image.png': data = new TestByteData(1.0); break; + case 'assets/1.0x/image.png': + data = new TestByteData(10.0); // see "...with a main asset and a 1.0x asset" + break; case 'assets/1.5x/image.png': data = new TestByteData(1.5); break; @@ -72,7 +80,7 @@ class TestAssetBundle extends CachingAssetBundle { @override Future loadString(String key, { bool cache: true }) { if (key == 'AssetManifest.json') - return new SynchronousFuture(testManifest); + return new SynchronousFuture(manifest); return null; } @@ -101,7 +109,7 @@ class TestAssetImage extends AssetImage { } } -Widget buildImageAtRatio(String image, Key key, double ratio, bool inferSize) { +Widget buildImageAtRatio(String image, Key key, double ratio, bool inferSize, [AssetBundle bundle]) { const double windowSize = 500.0; // 500 logical pixels const double imageSize = 200.0; // 200 logical pixels @@ -112,7 +120,7 @@ Widget buildImageAtRatio(String image, Key key, double ratio, bool inferSize) { padding: const EdgeInsets.all(0.0) ), child: new DefaultAssetBundle( - bundle: new TestAssetBundle(), + bundle: bundle ?? new TestAssetBundle(), child: new Center( child: inferSize ? new Image( @@ -231,4 +239,57 @@ void main() { expect(getTestImage(tester, key).scale, 4.0); }); + 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" + ] + } + '''; + final AssetBundle bundle = new TestAssetBundle(manifest: manifest); + + const double ratio = 1.0; + Key key = new GlobalKey(); + 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); + key = new GlobalKey(); + 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" + ] + } + '''; + final AssetBundle bundle = new TestAssetBundle(manifest: manifest); + + const double ratio = 1.0; + Key key = new GlobalKey(); + 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); + key = new GlobalKey(); + 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); + }); + } diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 7e4c04f0390..aef088249b9 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -98,6 +98,10 @@ class AssetBundle { final PackageMap packageMap = new PackageMap(packagesPath); + // The _assetVariants map contains an entry for each asset listed + // in the pubspec.yaml file's assets and font and sections. The + // value of each image asset is a list of resolution-specific "variants", + // see _AssetDirectoryCache. final Map<_Asset, List<_Asset>> assetVariants = _parseAssets( packageMap, manifestDescriptor, @@ -112,14 +116,24 @@ class AssetBundle { manifestDescriptor.containsKey('uses-material-design') && manifestDescriptor['uses-material-design']; + // Save the contents of each image, image variant, and font + // asset in entries. for (_Asset asset in assetVariants.keys) { if (!asset.assetFileExists && assetVariants[asset].isEmpty) { printStatus('Error detected in pubspec.yaml:', emphasis: true); printError('No file or variants found for $asset.\n'); return 1; } - if (asset.assetFileExists) - entries[asset.assetEntry] = new DevFSFileContent(asset.assetFile); + // The file name for an asset's "main" entry is whatever appears in + // the pubspec.yaml file. The main entry's file must always exist for + // font assets. It need not exist for an image if resolution-specific + // variant files exist. An image's main entry is treated the same as a + // "1x" resolution variant and if both exist then the explicit 1x + // variant is preferred. + if (asset.assetFileExists) { + assert(!assetVariants[asset].contains(asset)); + assetVariants[asset].insert(0, asset); + } for (_Asset variant in assetVariants[asset]) { assert(variant.assetFileExists); entries[variant.assetEntry] = new DevFSFileContent(variant.assetFile);