Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
496efca1
Unverified
Commit
496efca1
authored
Nov 05, 2020
by
Yegor
Committed by
GitHub
Nov 05, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
choose higher-res images on low-DPR screens (#69799)
parent
e8efde6a
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
115 additions
and
12 deletions
+115
-12
image_resolution.dart
packages/flutter/lib/src/painting/image_resolution.dart
+64
-10
image_resolution_test.dart
packages/flutter/test/widgets/image_resolution_test.dart
+51
-2
No files found.
packages/flutter/lib/src/painting/image_resolution.dart
View file @
496efca1
...
...
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:collection'
;
import
'dart:convert'
;
...
...
@@ -15,6 +14,11 @@ import 'image_provider.dart';
const
String
_kAssetManifestFileName
=
'AssetManifest.json'
;
/// A screen with a device-pixel ratio strictly less than this value is
/// considered a low-resolution screen (typically entry-level to mid-range
/// laptops, desktop screens up to QHD, low-end tablets such as Kindle Fire).
const
double
_kLowDprLimit
=
2.0
;
/// Fetches an image from an [AssetBundle], having determined the exact image to
/// use based on the context.
///
...
...
@@ -34,18 +38,52 @@ const String _kAssetManifestFileName = 'AssetManifest.json';
///
/// For example, suppose an application wants to use an icon named
/// "heart.png". This icon has representations at 1.0 (the main icon), as well
/// as
1.5 and 2.0 pixel ratios (variants). The asset bundle should then contai
n
/// the following assets:
/// as
2.0 and 4.0 pixel ratios (variants). The asset bundle should the
n
///
contain
the following assets:
///
/// ```
/// heart.png
/// 1.5x/heart.png
/// 2.0x/heart.png
/// 4.0x/heart.png
/// ```
///
/// On a device with a 1.0 device pixel ratio, the image chosen would be
/// heart.png; on a device with a 1.3 device pixel ratio, the image chosen
/// would be 1.5x/heart.png.
/// heart.png; on a device with a 2.0 device pixel ratio, the image chosen
/// would be 2.0x/heart.png; on a device with a 4.0 device pixel ratio, the
/// image chosen would be 4.0x/heart.png.
///
/// On a device with a device pixel ratio that does not exactly match an
/// available asset the "best match" is chosen. Which asset is the best depends
/// on the screen. Low-resolution screens (those with device pixel ratio
/// strictly less than 2.0) use a different matching algorithm from the
/// high-resolution screen. Because in low-resolution screens the physical
/// pixels are visible to the user upscaling artifacts (e.g. blurred edges) are
/// more pronounced. Therefore, a higher resolution asset is chosen, if
/// available. For higher-resolution screens, where individual physical pixels
/// are not visible to the user, the asset variant with the pixel ratio that's
/// the closest to the screen's device pixel ratio is chosen.
///
/// For example, for a screen with device pixel ratio 1.25 the image chosen
/// would be 2.0x/heart.png, even though heart.png (i.e. 1.0) is closer. This
/// is because the screen is considered low-resolution. For a screen with
/// device pixel ratio of 2.25 the image chosen would also be 2.0x/heart.png.
/// This is because the screen is considered to be a high-resolution screen,
/// and therefore upscaling a 2.0x image to 2.25 won't result in visible
/// upscaling artifacts. However, for a screen with device-pixel ratio 3.25 the
/// image chosen would be 4.0x/heart.png because it's closer to 4.0 than it is
/// to 2.0.
///
/// Choosing a higher-resolution image than necessary may waste significantly
/// more memory if the difference between the screen device pixel ratio and
/// the device pixel ratio of the image is high. To reduce memory usage,
/// consider providing more variants of the image. In the example above adding
/// a 3.0x/heart.png variant would improve memory usage for screens with device
/// pixel ratios between 3.0 and 3.5.
///
/// [ImageConfiguration] can be used to customize the selection of the image
/// variant by setting [ImageConfiguration.devicePixelRatio] to value different
/// from the default. The default value is derived from
/// [MediaQueryData.devicePixelRatio] by [createLocalImageConfiguration].
///
/// The directory level of the asset does not matter as long as the variants are
/// at the equivalent level; that is, the following is also a valid bundle
...
...
@@ -240,11 +278,22 @@ class AssetImage extends AssetBundleImageProvider {
// TODO(ianh): implement support for config.locale, config.textDirection,
// config.size, config.platform (then document this over in the Image.asset
// docs)
return
_find
Neares
t
(
mapping
,
config
.
devicePixelRatio
!);
return
_find
BestVarian
t
(
mapping
,
config
.
devicePixelRatio
!);
}
// Return the value for the key in a [SplayTreeMap] nearest the provided key.
String
?
_findNearest
(
SplayTreeMap
<
double
,
String
>
candidates
,
double
value
)
{
// Returns the "best" asset variant amongst the availabe `candidates`.
//
// The best variant is chosen as follows:
// - Choose a variant whose key matches `value` exactly, if available.
// - If `value` is less than the lowest key, choose the variant with the
// lowest key.
// - If `value` is greater than the highest key, choose the variant with
// the highest key.
// - If the screen has low device pixel ratio, chosse the variant with the
// lowest key higher than `value`.
// - If the screen has high device pixel ratio, choose the variant with the
// key nearest to `value`.
String
?
_findBestVariant
(
SplayTreeMap
<
double
,
String
>
candidates
,
double
value
)
{
if
(
candidates
.
containsKey
(
value
))
return
candidates
[
value
]!;
final
double
?
lower
=
candidates
.
lastKeyBefore
(
value
);
...
...
@@ -253,7 +302,12 @@ class AssetImage extends AssetBundleImageProvider {
return
candidates
[
upper
];
if
(
upper
==
null
)
return
candidates
[
lower
];
if
(
value
>
(
lower
+
upper
)
/
2
)
// On screens with low device-pixel ratios the artifacts from upscaling
// images are more visible than on screens with a higher device-pixel
// ratios because the physical pixels are larger. Choose the higher
// resolution image in that case instead of the nearest one.
if
(
value
<
_kLowDprLimit
||
value
>
(
lower
+
upper
)
/
2
)
return
candidates
[
upper
];
else
return
candidates
[
lower
];
...
...
packages/flutter/test/widgets/image_resolution_test.dart
View file @
496efca1
...
...
@@ -215,16 +215,19 @@ void main() {
expect
(
getRenderImage
(
tester
,
key
).
scale
,
1.5
);
});
// A 1.75 DPR screen is typically a low-resolution screen, such that physical
// pixels are visible to the user. For such screens we prefer to pick the
// higher resolution image, if available.
testWidgets
(
'Image for device pixel ratio 1.75'
,
(
WidgetTester
tester
)
async
{
const
double
ratio
=
1.75
;
Key
key
=
GlobalKey
();
await
pumpTreeToLayout
(
tester
,
buildImageAtRatio
(
image
,
key
,
ratio
,
false
,
images
));
expect
(
getRenderImage
(
tester
,
key
).
size
,
const
Size
(
200.0
,
200.0
));
expect
(
getRenderImage
(
tester
,
key
).
scale
,
1.5
);
expect
(
getRenderImage
(
tester
,
key
).
scale
,
2.0
);
key
=
GlobalKey
();
await
pumpTreeToLayout
(
tester
,
buildImageAtRatio
(
image
,
key
,
ratio
,
true
,
images
));
expect
(
getRenderImage
(
tester
,
key
).
size
,
const
Size
(
48.0
,
48.0
));
expect
(
getRenderImage
(
tester
,
key
).
scale
,
1.5
);
expect
(
getRenderImage
(
tester
,
key
).
scale
,
2.0
);
});
testWidgets
(
'Image for device pixel ratio 2.3'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -336,4 +339,50 @@ void main() {
expect
(
getRenderImage
(
tester
,
key
).
size
,
const
Size
(
5.0
,
5.0
));
});
// For low-resolution screens we prefer higher-resolution images due to
// visible physical pixel size (see the test for 1.75 DPR above). However,
// if higher resolution assets are not available we will pick the best
// available.
testWidgets
(
'Low-resolution assets'
,
(
WidgetTester
tester
)
async
{
final
AssetBundle
bundle
=
TestAssetBundle
(
manifest:
'''
{
"assets/image.png" : [
"assets/image.png",
"assets/1.5x/image.png"
]
}
'''
);
Future
<
void
>
testRatio
({
required
double
ratio
,
required
double
expectedScale
})
async
{
Key
key
=
GlobalKey
();
await
pumpTreeToLayout
(
tester
,
buildImageAtRatio
(
image
,
key
,
ratio
,
false
,
images
,
bundle
));
expect
(
getRenderImage
(
tester
,
key
).
size
,
const
Size
(
200.0
,
200.0
));
expect
(
getRenderImage
(
tester
,
key
).
scale
,
expectedScale
);
key
=
GlobalKey
();
await
pumpTreeToLayout
(
tester
,
buildImageAtRatio
(
image
,
key
,
ratio
,
true
,
images
,
bundle
));
expect
(
getRenderImage
(
tester
,
key
).
size
,
const
Size
(
48.0
,
48.0
));
expect
(
getRenderImage
(
tester
,
key
).
scale
,
expectedScale
);
}
// Choose higher resolution image as it's the lowest available.
await
testRatio
(
ratio:
0.25
,
expectedScale:
1.0
);
await
testRatio
(
ratio:
0.5
,
expectedScale:
1.0
);
await
testRatio
(
ratio:
0.75
,
expectedScale:
1.0
);
await
testRatio
(
ratio:
1.0
,
expectedScale:
1.0
);
// Choose higher resolution image even though a lower resolution
// image is closer.
await
testRatio
(
ratio:
1.20
,
expectedScale:
1.5
);
// Choose higher resolution image because it's closer.
await
testRatio
(
ratio:
1.25
,
expectedScale:
1.5
);
await
testRatio
(
ratio:
1.5
,
expectedScale:
1.5
);
// Choose lower resolution image because no higher resolution assets
// are not available.
await
testRatio
(
ratio:
1.75
,
expectedScale:
1.5
);
await
testRatio
(
ratio:
2.0
,
expectedScale:
1.5
);
await
testRatio
(
ratio:
2.25
,
expectedScale:
1.5
);
await
testRatio
(
ratio:
10.0
,
expectedScale:
1.5
);
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment