Commit bf115ec5 authored by Kris Giesing's avatar Kris Giesing

Add scale awareness to images

parent 9e784f0c
......@@ -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);
double getScale(String key) {
Match match = _extractRatioRegExp.firstMatch(key);
if (match != null && match.groupCount > 0) {
double resolution = double.parse(match.group(1));
result[resolution] = candidate;
}
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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment