Skip to content
Merged
6 changes: 1 addition & 5 deletions packages/flutter/lib/src/painting/image_resolution.dart
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,10 @@ class AssetImage extends AssetBundleImageProvider {
}

AssetMetadata _chooseVariant(String mainAssetKey, ImageConfiguration config, Iterable<AssetMetadata>? candidateVariants) {
if (candidateVariants == null) {
if (candidateVariants == null || candidateVariants.isEmpty || config.devicePixelRatio == null) {
return AssetMetadata(key: mainAssetKey, targetDevicePixelRatio: null, main: true);
}

if (config.devicePixelRatio == null) {
return candidateVariants.firstWhere((AssetMetadata variant) => variant.main);
}

final SplayTreeMap<double, AssetMetadata> candidatesByDevicePixelRatio =
SplayTreeMap<double, AssetMetadata>();
for (final AssetMetadata candidate in candidateVariants) {
Expand Down
23 changes: 11 additions & 12 deletions packages/flutter/lib/src/services/asset_manifest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ abstract class AssetManifest {
/// Retrieves metadata about an asset and its variants. Returns null if the
/// key was not found in the asset manifest.
///
/// This method considers a main asset to be a variant of itself and
/// includes it in the returned list.
/// This method considers a main asset to be a variant of itself. The returned
/// list will include it if it exists.
List<AssetMetadata>? getAssetVariants(String key);
}

Expand Down Expand Up @@ -73,22 +73,21 @@ class _AssetManifestBin implements AssetManifest {
}
_typeCastedData[key] = ((_data[key] ?? <Object?>[]) as Iterable<Object?>)
.cast<Map<Object?, Object?>>()
.map((Map<Object?, Object?> data) => AssetMetadata(
.map((Map<Object?, Object?> data) {
final String asset = data['asset']! as String;
final Object? dpr = data['dpr'];
return AssetMetadata(
key: data['asset']! as String,
targetDevicePixelRatio: data['dpr']! as double,
main: false,
))
targetDevicePixelRatio: dpr as double?,
main: key == asset,
);
})
.toList();

_data.remove(key);
}

final AssetMetadata mainAsset = AssetMetadata(key: key,
targetDevicePixelRatio: null,
main: true
);

return <AssetMetadata>[mainAsset, ..._typeCastedData[key]!];
return _typeCastedData[key]!;
}

@override
Expand Down
22 changes: 11 additions & 11 deletions packages/flutter/test/painting/image_resolution_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ void main() {
final Map<String, List<Map<Object?, Object?>>> assetBundleMap =
<String, List<Map<Object?, Object?>>>{};

assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[];

final AssetImage assetImage = AssetImage(
mainAssetPath,
bundle: TestAssetBundle(assetBundleMap),
Expand Down Expand Up @@ -160,15 +158,17 @@ void main() {
double chosenAssetRatio,
String expectedAssetPath,
) {
final Map<String, List<Map<Object?, Object?>>> assetBundleMap =
<String, List<Map<Object?, Object?>>>{};

final Map<Object?, Object?> mainAssetVariantManifestEntry = <Object?, Object?>{};
mainAssetVariantManifestEntry['asset'] = variantPath;
mainAssetVariantManifestEntry['dpr'] = 3.0;
assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[mainAssetVariantManifestEntry];

final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap);
const Map<String, List<Map<Object?, Object?>>> assetManifest =
<String, List<Map<Object?, Object?>>>{
'assets/normalFolder/normalFile.png': <Map<Object?, Object?>>[
<Object?, Object?>{'asset': 'assets/normalFolder/normalFile.png'},
<Object?, Object?>{
'asset': 'assets/normalFolder/3.0x/normalFile.png',
'dpr': 3.0
},
]
};
final TestAssetBundle testAssetBundle = TestAssetBundle(assetManifest);

final AssetImage assetImage = AssetImage(
mainAssetPath,
Expand Down
40 changes: 37 additions & 3 deletions packages/flutter/test/services/asset_manifest_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

Expand All @@ -11,12 +13,19 @@ class TestAssetBundle extends AssetBundle {
if (key == 'AssetManifest.smcbin') {
final Map<String, List<Object>> binManifestData = <String, List<Object>>{
'assets/foo.png': <Object>[
<String, Object>{
'asset': 'assets/foo.png',
},
<String, Object>{
'asset': 'assets/2x/foo.png',
'dpr': 2.0
}
},
],
'assets/bar.png': <Object>[
<String, Object>{
'asset': 'assets/bar.png',
},
],
'assets/bar.png': <Object>[],
};

final ByteData data = const StandardMessageCodec().encodeMessage(binManifestData)!;
Expand All @@ -32,7 +41,6 @@ class TestAssetBundle extends AssetBundle {
}
}


void main() {
TestWidgetsFlutterBinding.ensureInitialized();

Expand Down Expand Up @@ -65,3 +73,29 @@ void main() {
expect(manifest.getAssetVariants('invalid asset key'), isNull);
});
}

String createAssetManifestJson(Map<String, List<AssetMetadata>> manifest) {
final Map<Object, Object> jsonObject = manifest.map(
(String key, List<AssetMetadata> value) {
final List<String> variants = value.map((AssetMetadata e) => e.key).toList();
return MapEntry<String, List<String>>(key, variants);
}
);

return json.encode(jsonObject);
}

ByteData createAssetManifestSmcBin(Map<String, List<AssetMetadata>> manifest) {
final Map<Object, Object> smcObject = manifest.map(
(String key, List<AssetMetadata> value) {
final List<Object> variants = value.map((AssetMetadata variant) => <String, Object?>{
'asset': variant.key,
'dpr': variant.targetDevicePixelRatio,
}).toList();

return MapEntry<String, List<Object>>(key, variants);
}
);

return const StandardMessageCodec().encodeMessage(smcObject)!;
}
32 changes: 15 additions & 17 deletions packages/flutter/test/widgets/image_resolution_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ import '../image_data.dart';
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
double scaleOf(ByteData data) => data.getFloat64(0);

final Map<Object?, Object?> testManifest = json.decode('''
{
"assets/image.png" : [
{"asset": "assets/1.5x/image.png", "dpr": 1.5},
{"asset": "assets/2.0x/image.png", "dpr": 2.0},
{"asset": "assets/3.0x/image.png", "dpr": 3.0},
{"asset": "assets/4.0x/image.png", "dpr": 4.0}
]
}
''') as Map<Object?, Object?>;
final Map<Object?, Object?> testManifest = <Object?, Object?>{
'assets/image.png' : <Map<String, Object>>[
<String, String>{'asset': 'assets/image.png'},
<String, Object>{'asset': 'assets/1.5x/image.png', 'dpr': 1.5},
<String, Object>{'asset': 'assets/2.0x/image.png', 'dpr': 2.0},
<String, Object>{'asset': 'assets/3.0x/image.png', 'dpr': 3.0},
<String, Object>{'asset': 'assets/4.0x/image.png', 'dpr': 4.0}
],
};

class TestAssetBundle extends CachingAssetBundle {
TestAssetBundle({ required Map<Object?, Object?> manifest }) {
Expand Down Expand Up @@ -303,13 +302,12 @@ void main() {
// if higher resolution assets are not available we will pick the best
// available.
testWidgets('Low-resolution assets', (WidgetTester tester) async {
final Map<Object?, Object?> manifest = json.decode('''
{
"assets/image.png" : [
{"asset": "assets/1.5x/image.png", "dpr": 1.5}
]
}
''') as Map<Object?, Object?>;
const Map<Object?, Object?> manifest = <Object?, Object?>{
'assets/image.png': <Map<String, Object>>[
<String, Object>{'asset': 'assets/image.png'},
<String, Object>{'asset': 'assets/1.5x/image.png', 'dpr': 1.5},
],
};
final AssetBundle bundle = TestAssetBundle(manifest: manifest);

Future<void> testRatio({required double ratio, required double expectedScale}) async {
Expand Down
17 changes: 7 additions & 10 deletions packages/flutter_tools/lib/src/asset.dart
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ class ManifestAssetBundle implements AssetBundle {
DateTime? _lastBuildTimestamp;

// We assume the main asset is designed for a device pixel ratio of 1.0.
static const double _defaultResolution = 1.0;
static const String _kAssetManifestJsonFilename = 'AssetManifest.json';
static const String _kAssetManifestBinFilename = 'AssetManifest.smcbin';

Expand Down Expand Up @@ -688,7 +687,7 @@ class ManifestAssetBundle implements AssetBundle {
DevFSByteContent _createAssetManifestBinary(
Map<String, List<String>> assetManifest
) {
double parseScale(String key) {
double? parseScale(String key) {
final Uri assetUri = Uri.parse(key);
String directoryPath = '';
if (assetUri.pathSegments.length > 1) {
Expand All @@ -699,7 +698,8 @@ class ManifestAssetBundle implements AssetBundle {
if (match != null && match.groupCount > 0) {
return double.parse(match.group(1)!);
}
return _defaultResolution;

return null;
}

final Map<String, dynamic> result = <String, dynamic>{};
Expand All @@ -708,15 +708,12 @@ class ManifestAssetBundle implements AssetBundle {
final List<dynamic> resultVariants = <dynamic>[];
final List<String> entries = (manifestEntry.value as List<dynamic>).cast<String>();
for (final String variant in entries) {
if (variant == manifestEntry.key) {
// With the newer binary format, don't include the main asset in it's
// list of variants. This reduces parsing time at runtime.
continue;
}
final Map<String, dynamic> resultVariant = <String, dynamic>{};
final double variantDevicePixelRatio = parseScale(variant);
final double? variantDevicePixelRatio = parseScale(variant);
resultVariant['asset'] = variant;
resultVariant['dpr'] = variantDevicePixelRatio;
if (variantDevicePixelRatio != null) {
resultVariant['dpr'] = variantDevicePixelRatio;
}
resultVariants.add(resultVariant);
}
result[manifestEntry.key] = resultVariants;
Expand Down
Loading