From 6c768da91a66e9fd7ad6c8a8961130265b22c266 Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Sat, 25 Apr 2020 15:58:22 +0530 Subject: [PATCH] Improve thumbnail generation logic --- lib/core/constants.dart | 2 + lib/core/thumbnail_cache.dart | 28 ++++++++++--- lib/models/photo.dart | 6 ++- lib/ui/album_list_widget.dart | 2 +- lib/ui/thumbnail_widget.dart | 78 ++++++++++++++++++++--------------- lib/ui/zoomable_image.dart | 7 +++- 6 files changed, 80 insertions(+), 43 deletions(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 5f79b650c..ead10a8c7 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,2 +1,4 @@ const String ENDPOINT = "http://192.168.0.255:8080"; const String USER = "umbu"; // TODO: Fix me +const int THUMBNAIL_SMALL_SIZE = 128; +const int THUMBNAIL_LARGE_SIZE = 512; diff --git a/lib/core/thumbnail_cache.dart b/lib/core/thumbnail_cache.dart index 5b0ac8e99..a7433eded 100644 --- a/lib/core/thumbnail_cache.dart +++ b/lib/core/thumbnail_cache.dart @@ -4,13 +4,31 @@ import 'package:myapp/core/lru_map.dart'; import 'package:myapp/models/photo.dart'; class ThumbnailLruCache { - static LRUMap _map = LRUMap(500); + static LRUMap<_ThumbnailCacheKey, Uint8List> _map = LRUMap(5000); - static Uint8List get(Photo photo) { - return _map.get(photo.generatedId); + static Uint8List get(Photo photo, int size) { + return _map.get(_ThumbnailCacheKey(photo, size)); } - static void put(Photo photo, Uint8List imageData) { - _map.put(photo.generatedId, imageData); + static void put(Photo photo, int size, Uint8List imageData) { + _map.put(_ThumbnailCacheKey(photo, size), imageData); } } + +class _ThumbnailCacheKey { + Photo photo; + int size; + + _ThumbnailCacheKey(this.photo, this.size); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is _ThumbnailCacheKey && + runtimeType == other.runtimeType && + photo.generatedId == other.photo.generatedId && + size == other.size; + + @override + int get hashCode => photo.hashCode * size.hashCode; +} diff --git a/lib/models/photo.dart b/lib/models/photo.dart index c29cb178d..94883a976 100644 --- a/lib/models/photo.dart +++ b/lib/models/photo.dart @@ -32,8 +32,12 @@ class Photo { return photo; } + AssetEntity getAsset() { + return AssetEntity(id: localId); + } + Future getBytes({int quality = 100}) { - final asset = AssetEntity(id: localId); + final asset = getAsset(); if (extension(title) == ".HEIC" || quality != 100) { return asset.originBytes.then((bytes) => FlutterImageCompress.compressWithList(bytes, quality: quality) diff --git a/lib/ui/album_list_widget.dart b/lib/ui/album_list_widget.dart index 48bce4ec4..77105ca65 100644 --- a/lib/ui/album_list_widget.dart +++ b/lib/ui/album_list_widget.dart @@ -59,7 +59,7 @@ class _AlbumListWidgetState extends State { return GestureDetector( child: Column( children: [ - ThumbnailWidget(album.photos[0], size: 140), + ThumbnailWidget(album.photos[0]), Padding(padding: EdgeInsets.all(2)), Expanded( child: Text( diff --git a/lib/ui/thumbnail_widget.dart b/lib/ui/thumbnail_widget.dart index c9c0ce48b..81cfaab33 100644 --- a/lib/ui/thumbnail_widget.dart +++ b/lib/ui/thumbnail_widget.dart @@ -1,18 +1,15 @@ -import 'dart:typed_data'; - import 'package:flutter/material.dart'; +import 'package:logger/logger.dart'; import 'package:myapp/core/thumbnail_cache.dart'; import 'package:myapp/models/photo.dart'; -import 'package:photo_manager/photo_manager.dart'; +import 'package:myapp/core/constants.dart'; class ThumbnailWidget extends StatefulWidget { final Photo photo; - final int size; const ThumbnailWidget( this.photo, { Key key, - this.size, }) : super(key: key); @override _ThumbnailWidgetState createState() => _ThumbnailWidgetState(); @@ -24,41 +21,54 @@ class _ThumbnailWidgetState extends State { color: Colors.grey[500], ); + bool _loadedSmallThumbnail = false; + bool _loadedLargeThumbnail = false; + ImageProvider _imageProvider; + @override Widget build(BuildContext context) { - final size = widget.size == null ? 512 : widget.size; - final cachedImageData = ThumbnailLruCache.get(widget.photo); - - Widget image; - - if (cachedImageData != null) { - image = _buildImage(cachedImageData, size); - } else { - if (widget.photo.localId != null) { - image = FutureBuilder( - future: AssetEntity(id: widget.photo.localId) - .thumbDataWithSize(size, size), - builder: (context, snapshot) { - if (snapshot.hasData) { - ThumbnailLruCache.put(widget.photo, snapshot.data); - Image image = _buildImage(snapshot.data, size); - return image; - } else { - return loadingWidget; - } - }, - ); + if (!_loadedSmallThumbnail && !_loadedLargeThumbnail) { + final cachedSmallThumbnail = + ThumbnailLruCache.get(widget.photo, THUMBNAIL_SMALL_SIZE); + if (cachedSmallThumbnail != null) { + _imageProvider = Image.memory(cachedSmallThumbnail).image; + _loadedSmallThumbnail = true; } else { - // TODO - return Text("Not Implemented"); + if (mounted) { + widget.photo + .getAsset() + .thumbDataWithSize(THUMBNAIL_SMALL_SIZE, THUMBNAIL_SMALL_SIZE) + .then((data) { + if (mounted) { + setState(() { + if (data != null) { + _imageProvider = Image.memory(data).image; + } + _loadedSmallThumbnail = true; + }); + } + ThumbnailLruCache.put(widget.photo, THUMBNAIL_SMALL_SIZE, data); + }); + } } } - return image; - } + if (!_loadedLargeThumbnail) { + if (ThumbnailLruCache.get(widget.photo, THUMBNAIL_LARGE_SIZE) == null) { + widget.photo + .getAsset() + .thumbDataWithSize(THUMBNAIL_LARGE_SIZE, THUMBNAIL_LARGE_SIZE) + .then((data) { + ThumbnailLruCache.put(widget.photo, THUMBNAIL_LARGE_SIZE, data); + }); + } + } - Image _buildImage(Uint8List data, int size) { - return Image.memory(data, - width: size.toDouble(), height: size.toDouble(), fit: BoxFit.cover); + if (_imageProvider != null) { + return Image( + image: _imageProvider, gaplessPlayback: true, fit: BoxFit.cover); + } else { + return loadingWidget; + } } } diff --git a/lib/ui/zoomable_image.dart b/lib/ui/zoomable_image.dart index 2b93c5e2c..09f724733 100644 --- a/lib/ui/zoomable_image.dart +++ b/lib/ui/zoomable_image.dart @@ -2,11 +2,11 @@ import 'dart:typed_data'; import 'package:flutter/widgets.dart'; import 'package:logger/logger.dart'; import 'package:myapp/core/image_cache.dart'; -import 'package:myapp/core/lru_map.dart'; import 'package:myapp/core/thumbnail_cache.dart'; import 'package:myapp/models/photo.dart'; import 'package:myapp/ui/loading_widget.dart'; import 'package:photo_view/photo_view.dart'; +import 'package:myapp/core/constants.dart'; class ZoomableImage extends StatefulWidget { final Photo photo; @@ -41,10 +41,13 @@ class _ZoomableImageState extends State { @override Widget build(BuildContext context) { if (!_loadedThumbnail && !_loadedFinalImage) { - final cachedThumbnail = ThumbnailLruCache.get(widget.photo); + final cachedThumbnail = + ThumbnailLruCache.get(widget.photo, THUMBNAIL_LARGE_SIZE); if (cachedThumbnail != null) { _imageProvider = Image.memory(cachedThumbnail).image; _loadedThumbnail = true; + } else { + Logger().i("Thumbnail missing for " + widget.photo.toString()); } }