Unverified Commit 86dd664f authored by gaaclarke's avatar gaaclarke Committed by GitHub

Made the behavior for caching large images modular. (#46010)

Introduced LargeImageHandler to ImageCache class.
parent ec0842e0
...@@ -7,6 +7,9 @@ import 'image_stream.dart'; ...@@ -7,6 +7,9 @@ import 'image_stream.dart';
const int _kDefaultSize = 1000; const int _kDefaultSize = 1000;
const int _kDefaultSizeBytes = 100 << 20; // 100 MiB const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
/// Function used by [ImageCache.largeImageHandler].
typedef LargeImageHandler = void Function(ImageCache, int);
/// Class for caching images. /// Class for caching images.
/// ///
/// Implements a least-recently-used cache of up to 1000 images, and up to 100 /// Implements a least-recently-used cache of up to 1000 images, and up to 100
...@@ -90,6 +93,28 @@ class ImageCache { ...@@ -90,6 +93,28 @@ class ImageCache {
int get currentSizeBytes => _currentSizeBytes; int get currentSizeBytes => _currentSizeBytes;
int _currentSizeBytes = 0; int _currentSizeBytes = 0;
/// Callback that is executed when inserting an image whose byte size is
/// larger than the [maximumByteSize]. Editing the [maximumByteSize] in the
/// callback can accomodate for the image. Set to `null` for the default
/// behavior, which is to increase the [maximumByteSize] to accomodate the
/// large image.
///
/// {@tool sample}
///
/// Here is an example implementation that increases the cache size in
/// response to a large image:
/// ```dart
/// void handler(ImageCache imageCache, int imageSize) {
/// final int newSize = imageSize + 1000;
/// imageCache.maximumSizeBytes = newSize;
/// print("Increase image cache size: $newSize");
/// }
/// ```
set largeImageHandler(LargeImageHandler handler) {
_largeImageHandler = handler;
}
LargeImageHandler _largeImageHandler;
/// Evicts all entries from the cache. /// Evicts all entries from the cache.
/// ///
/// This is useful if, for instance, the root asset bundle has been updated /// This is useful if, for instance, the root asset bundle has been updated
...@@ -170,12 +195,16 @@ class ImageCache { ...@@ -170,12 +195,16 @@ class ImageCache {
// Images that fail to load don't contribute to cache size. // Images that fail to load don't contribute to cache size.
final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4; final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
final _CachedImage image = _CachedImage(result, imageSize); final _CachedImage image = _CachedImage(result, imageSize);
// If the image is bigger than the maximum cache size, and the cache size
// is not zero, then increase the cache size to the size of the image plus if (_isImageTooLarge(imageSize)) {
// some change. final LargeImageHandler handler = _largeImageHandler ?? _bumpUpMaximumSizeLargeImageHandler;
if (maximumSizeBytes > 0 && imageSize > maximumSizeBytes) { handler(this, imageSize);
_maximumSizeBytes = imageSize + 1000; if (_isImageTooLarge(imageSize)) {
// Abort insertion of image, it doesn't fit.
return;
}
} }
_currentSizeBytes += imageSize; _currentSizeBytes += imageSize;
final _PendingImage pendingImage = _pendingImages.remove(key); final _PendingImage pendingImage = _pendingImages.remove(key);
if (pendingImage != null) { if (pendingImage != null) {
...@@ -194,6 +223,14 @@ class ImageCache { ...@@ -194,6 +223,14 @@ class ImageCache {
return result; return result;
} }
bool _isImageTooLarge(int imageSize) {
return maximumSizeBytes > 0 && imageSize > maximumSizeBytes;
}
static void _bumpUpMaximumSizeLargeImageHandler(ImageCache imageCache, int imageSize) {
imageCache.maximumSizeBytes = imageSize + 1000;
}
// Remove images from the cache until both the length and bytes are below // Remove images from the cache until both the length and bytes are below
// maximum, or the cache is empty. // maximum, or the cache is empty.
void _checkCacheSize() { void _checkCacheSize() {
......
...@@ -15,6 +15,7 @@ void main() { ...@@ -15,6 +15,7 @@ void main() {
}); });
tearDown(() { tearDown(() {
imageCache.largeImageHandler = null;
imageCache.clear(); imageCache.clear();
imageCache.maximumSize = 1000; imageCache.maximumSize = 1000;
imageCache.maximumSizeBytes = 10485760; imageCache.maximumSizeBytes = 10485760;
...@@ -131,6 +132,25 @@ void main() { ...@@ -131,6 +132,25 @@ void main() {
expect(imageCache.maximumSizeBytes, 256 + 1000); expect(imageCache.maximumSizeBytes, 256 + 1000);
}); });
test('Large image handler that rejects an image.', () async {
bool wasCalled = false;
imageCache.largeImageHandler = (ImageCache imageCache, int imageSize) { wasCalled = true; };
const TestImage testImage1 = TestImage(width: 8, height: 8);
const TestImage testImage2 = TestImage(width: 16, height: 16);
imageCache.maximumSizeBytes = 256;
await extractOneFrame(const TestImageProvider(1, 1, image: testImage1).resolve(ImageConfiguration.empty));
expect(imageCache.currentSize, 1);
expect(imageCache.currentSizeBytes, 256);
expect(imageCache.maximumSizeBytes, 256);
await extractOneFrame(const TestImageProvider(2, 2, image: testImage2).resolve(ImageConfiguration.empty));
expect(imageCache.currentSize, 1);
expect(imageCache.currentSizeBytes, 256);
expect(imageCache.maximumSizeBytes, 256);
expect(wasCalled, isTrue);
});
test('Returns null if an error is caught resolving an image', () { test('Returns null if an error is caught resolving an image', () {
final ErrorImageProvider errorImage = ErrorImageProvider(); final ErrorImageProvider errorImage = ErrorImageProvider();
expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null)), throwsA(isInstanceOf<Error>())); expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, null)), throwsA(isInstanceOf<Error>()));
......
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