mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add scale awareness to images
This commit is contained in:
parent
9e784f0cd3
commit
bf115ec5b0
@ -22,6 +22,7 @@ class RenderImage extends RenderBox {
|
||||
ui.Image image,
|
||||
double width,
|
||||
double height,
|
||||
double scale,
|
||||
Color color,
|
||||
ImageFit fit,
|
||||
FractionalOffset alignment,
|
||||
@ -30,6 +31,7 @@ class RenderImage extends RenderBox {
|
||||
}) : _image = image,
|
||||
_width = width,
|
||||
_height = height,
|
||||
_scale = scale,
|
||||
_color = color,
|
||||
_fit = fit,
|
||||
_alignment = alignment,
|
||||
@ -76,6 +78,18 @@ class RenderImage extends RenderBox {
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// If non-null, specify the image's scale.
|
||||
///
|
||||
/// Used when determining the best display size for the image.
|
||||
double get scale => _scale;
|
||||
double _scale;
|
||||
void set scale (double value) {
|
||||
if (value == _scale)
|
||||
return;
|
||||
_scale = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
ColorFilter _colorFilter;
|
||||
|
||||
// Should we make the transfer mode configurable?
|
||||
@ -161,8 +175,9 @@ class RenderImage extends RenderBox {
|
||||
if (constraints.isTight || _image == null)
|
||||
return constraints.smallest;
|
||||
|
||||
double width = _image.width.toDouble();
|
||||
double height = _image.height.toDouble();
|
||||
double scale = _scale ?? 1.0;
|
||||
double width = _image.width.toDouble() / scale;
|
||||
double height = _image.height.toDouble() / scale;
|
||||
assert(width > 0.0);
|
||||
assert(height > 0.0);
|
||||
double aspectRatio = width / height;
|
||||
|
||||
@ -43,16 +43,16 @@ class NetworkAssetBundle extends AssetBundle {
|
||||
}
|
||||
|
||||
abstract class CachingAssetBundle extends AssetBundle {
|
||||
Map<String, ImageResource> _imageCache = new Map<String, ImageResource>();
|
||||
Map<String, ImageResource> imageCache = new Map<String, ImageResource>();
|
||||
Map<String, Future<String>> _stringCache = new Map<String, Future<String>>();
|
||||
|
||||
Future<ui.Image> _fetchImage(String key) async {
|
||||
Future<ui.Image> fetchImage(String key) async {
|
||||
return await decodeImageFromDataPipe(await load(key));
|
||||
}
|
||||
|
||||
ImageResource loadImage(String key) {
|
||||
return _imageCache.putIfAbsent(key, () {
|
||||
return new ImageResource(_fetchImage(key));
|
||||
return imageCache.putIfAbsent(key, () {
|
||||
return new ImageResource(fetchImage(key));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -16,13 +16,14 @@ typedef void ImageListener(ui.Image image);
|
||||
/// image object might change over time, either because the image is animating
|
||||
/// or because the underlying image resource was mutated.
|
||||
class ImageResource {
|
||||
ImageResource(this._futureImage) {
|
||||
ImageResource(this._futureImage, { this.scale : 1.0 }) {
|
||||
_futureImage.then(_handleImageLoaded, onError: (exception, stack) => _handleImageError('Failed to load image:', exception, stack));
|
||||
}
|
||||
|
||||
bool _resolved = false;
|
||||
Future<ui.Image> _futureImage;
|
||||
ui.Image _image;
|
||||
double scale;
|
||||
final List<ImageListener> _listeners = new List<ImageListener>();
|
||||
|
||||
/// The first concrete [ui.Image] object represented by this handle.
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mojo/core.dart' as core;
|
||||
@ -29,12 +30,34 @@ class _ResolvingAssetBundle extends CachingAssetBundle {
|
||||
final AssetBundle bundle;
|
||||
final _AssetResolver resolver;
|
||||
|
||||
Map<String, String> _keyCache = <String, String>{};
|
||||
Map<String, String> keyCache = <String, String>{};
|
||||
|
||||
Future<core.MojoDataPipeConsumer> load(String key) async {
|
||||
if (!_keyCache.containsKey(key))
|
||||
_keyCache[key] = await resolver.resolve(key);
|
||||
return await bundle.load(_keyCache[key]);
|
||||
if (!keyCache.containsKey(key))
|
||||
keyCache[key] = await resolver.resolve(key);
|
||||
return await bundle.load(keyCache[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Asset bundle that understands how specific asset keys represent image scale.
|
||||
class _ResolutionAwareAssetBundle extends _ResolvingAssetBundle {
|
||||
_ResolutionAwareAssetBundle({
|
||||
AssetBundle bundle,
|
||||
_ResolutionAwareAssetResolver resolver
|
||||
}) : super(
|
||||
bundle: bundle,
|
||||
resolver: resolver
|
||||
);
|
||||
|
||||
_ResolutionAwareAssetResolver get resolver => super.resolver;
|
||||
|
||||
Future<ui.Image> fetchImage(String key) async {
|
||||
core.MojoDataPipeConsumer pipe = await load(key);
|
||||
// At this point the key should be in our key cache, and the image
|
||||
// resource should be in our image cache
|
||||
double scale = resolver.getScale(keyCache[key]);
|
||||
this.imageCache[key].scale = scale;
|
||||
return await decodeImageFromDataPipe(pipe);
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +81,7 @@ abstract class _VariantAssetResolver extends _AssetResolver {
|
||||
Future<String> resolve(String name) async {
|
||||
_initializer ??= _loadManifest();
|
||||
await _initializer;
|
||||
// If there's no asset manifest, just return the main asset always
|
||||
// If there's no asset manifest, just return the main asset
|
||||
if (_assetManifest == null)
|
||||
return name;
|
||||
// Allow references directly to variants: if the supplied name is not a
|
||||
@ -81,18 +104,16 @@ class _ResolutionAwareAssetResolver extends _VariantAssetResolver {
|
||||
|
||||
final double devicePixelRatio;
|
||||
|
||||
static final RegExp extractRatioRegExp = new RegExp(r"/?(\d+(\.\d*)?)x/");
|
||||
// We assume the main asset is designed for a device pixel ratio of 1.0
|
||||
static const double _naturalResolution = 1.0;
|
||||
static final RegExp _extractRatioRegExp = new RegExp(r"/?(\d+(\.\d*)?)x/");
|
||||
|
||||
SplayTreeMap<double, String> _buildMapping(List<String> candidates) {
|
||||
SplayTreeMap<double, String> result = new SplayTreeMap<double, String>();
|
||||
for (String candidate in candidates) {
|
||||
Match match = extractRatioRegExp.firstMatch(candidate);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
double resolution = double.parse(match.group(1));
|
||||
result[resolution] = candidate;
|
||||
}
|
||||
double getScale(String key) {
|
||||
Match match = _extractRatioRegExp.firstMatch(key);
|
||||
if (match != null && match.groupCount > 0) {
|
||||
return double.parse(match.group(1));
|
||||
}
|
||||
return result;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Return the value for the key in a [SplayTreeMap] nearest the provided key.
|
||||
@ -112,9 +133,11 @@ class _ResolutionAwareAssetResolver extends _VariantAssetResolver {
|
||||
}
|
||||
|
||||
String chooseVariant(String main, List<String> candidates) {
|
||||
SplayTreeMap<double, String> mapping = _buildMapping(candidates);
|
||||
// We assume the main asset is designed for a device pixel ratio of 1.0
|
||||
mapping[1.0] = main;
|
||||
SplayTreeMap<double, String> mapping = new SplayTreeMap<double, String>();
|
||||
for (String candidate in candidates) {
|
||||
mapping[getScale(candidate)] = candidate;
|
||||
}
|
||||
mapping[_naturalResolution] = main;
|
||||
return _findNearest(mapping, devicePixelRatio);
|
||||
}
|
||||
}
|
||||
@ -172,7 +195,7 @@ class _AssetVendorState extends State<AssetVendor> {
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bundle = new _ResolvingAssetBundle(
|
||||
_bundle = new _ResolutionAwareAssetBundle(
|
||||
bundle: config.bundle,
|
||||
resolver: new _ResolutionAwareAssetResolver(
|
||||
bundle: config.bundle,
|
||||
@ -184,7 +207,7 @@ class _AssetVendorState extends State<AssetVendor> {
|
||||
void didUpdateConfig(AssetVendor oldConfig) {
|
||||
if (config.bundle != oldConfig.bundle ||
|
||||
config.devicePixelRatio != oldConfig.devicePixelRatio) {
|
||||
_bundle = new _ResolvingAssetBundle(
|
||||
_bundle = new _ResolutionAwareAssetBundle(
|
||||
bundle: config.bundle,
|
||||
resolver: new _ResolutionAwareAssetResolver(
|
||||
bundle: config.bundle,
|
||||
|
||||
@ -1508,6 +1508,7 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
this.image,
|
||||
this.width,
|
||||
this.height,
|
||||
this.scale,
|
||||
this.color,
|
||||
this.fit,
|
||||
this.alignment,
|
||||
@ -1530,6 +1531,11 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
/// aspect ratio.
|
||||
final double height;
|
||||
|
||||
/// If non-null, specify the image's scale.
|
||||
///
|
||||
/// Used when determining the best display size for the image.
|
||||
final double scale;
|
||||
|
||||
/// If non-null, apply this color filter to the image before painting.
|
||||
final Color color;
|
||||
|
||||
@ -1559,6 +1565,7 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
image: image,
|
||||
width: width,
|
||||
height: height,
|
||||
scale: scale,
|
||||
color: color,
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
@ -1569,6 +1576,7 @@ class RawImage extends LeafRenderObjectWidget {
|
||||
renderObject.image = image;
|
||||
renderObject.width = width;
|
||||
renderObject.height = height;
|
||||
renderObject.scale = scale;
|
||||
renderObject.color = color;
|
||||
renderObject.alignment = alignment;
|
||||
renderObject.fit = fit;
|
||||
@ -1650,11 +1658,16 @@ class _ImageListenerState extends State<RawImageResource> {
|
||||
config.image.addListener(_handleImageChanged);
|
||||
}
|
||||
|
||||
// Note, we remember the resolved image and the scale together. If the
|
||||
// source image resource is updated, we shouldn't grab its scale until
|
||||
// it provides the associated resolved image.
|
||||
ui.Image _resolvedImage;
|
||||
double _scale;
|
||||
|
||||
void _handleImageChanged(ui.Image resolvedImage) {
|
||||
setState(() {
|
||||
_resolvedImage = resolvedImage;
|
||||
_scale = config.image.scale;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1675,6 +1688,7 @@ class _ImageListenerState extends State<RawImageResource> {
|
||||
image: _resolvedImage,
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
scale: _scale,
|
||||
color: config.color,
|
||||
fit: config.fit,
|
||||
alignment: config.alignment,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user