Skip to content

Commit 633ae1b

Browse files
EinTonisavely-krasovsky
authored andcommitted
perf(mobile): remove small thumbnail and cache generated thumbnails (immich-app#17792)
* Remove small thumbnail and cache generated thumbnails * Creating the small thumbnails takes quite some time, which should not be underestimated. * The time needed to generate the small or big thumbnail is not too different from each other. Therefore there is no real benefit of the small thumbnail and it only adds frustration to the end user experience. That is because the image appeared to have loaded (the visual move from blur to something better) but it's still so bad that it is basically a blur. The better solution is therefore to stay at the blur until the actual thumbnail has loaded. * Additionaly to the faster generation of the thumbnail, it now also gets cached similarly to the remote thumbnail which already gets cached. This further speeds up the all over usage of the app and prevents a repeatet thumbnail generation when opening the app. * Decreased the quality from the default 95 to 80 to provide similar quality with much reduces thumbnail size. * Use try catch around the read of the cache file. * Use the key provided in the loadImage method instead of the asset of the constructor. * Use userId instead of ownerId * Remove import * Add checksum to thumbnail cache key
1 parent 74b5112 commit 633ae1b

File tree

2 files changed

+45
-19
lines changed

2 files changed

+45
-19
lines changed

mobile/lib/providers/image/immich_local_thumbnail_provider.dart

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import 'dart:async';
22
import 'dart:ui' as ui;
33

44
import 'package:cached_network_image/cached_network_image.dart';
5+
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
6+
import 'package:immich_mobile/providers/image/cache/thumbnail_image_cache_manager.dart';
57

68
import 'package:flutter/foundation.dart';
79
import 'package:flutter/painting.dart';
810
import 'package:immich_mobile/entities/asset.entity.dart';
911
import 'package:photo_manager/photo_manager.dart' show ThumbnailSize;
12+
import 'package:logging/logging.dart';
1013

1114
/// The local image provider for an asset
1215
/// Only viable
@@ -15,11 +18,16 @@ class ImmichLocalThumbnailProvider
1518
final Asset asset;
1619
final int height;
1720
final int width;
21+
final CacheManager? cacheManager;
22+
final Logger log = Logger("ImmichLocalThumbnailProvider");
23+
final String? userId;
1824

1925
ImmichLocalThumbnailProvider({
2026
required this.asset,
2127
this.height = 256,
2228
this.width = 256,
29+
this.cacheManager,
30+
this.userId,
2331
}) : assert(asset.local != null, 'Only usable when asset.local is set');
2432

2533
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
@@ -36,11 +44,10 @@ class ImmichLocalThumbnailProvider
3644
ImmichLocalThumbnailProvider key,
3745
ImageDecoderCallback decode,
3846
) {
39-
final chunkEvents = StreamController<ImageChunkEvent>();
47+
final cache = cacheManager ?? ThumbnailImageCacheManager();
4048
return MultiImageStreamCompleter(
41-
codec: _codec(key.asset, decode, chunkEvents),
49+
codec: _codec(key.asset, cache, decode),
4250
scale: 1.0,
43-
chunkEvents: chunkEvents.stream,
4451
informationCollector: () sync* {
4552
yield ErrorDescription(key.asset.fileName);
4653
},
@@ -50,25 +57,38 @@ class ImmichLocalThumbnailProvider
5057
// Streams in each stage of the image as we ask for it
5158
Stream<ui.Codec> _codec(
5259
Asset assetData,
60+
CacheManager cache,
5361
ImageDecoderCallback decode,
54-
StreamController<ImageChunkEvent> chunkEvents,
5562
) async* {
56-
final thumbBytes = await assetData.local
57-
?.thumbnailDataWithSize(ThumbnailSize(width, height));
58-
if (thumbBytes == null) {
59-
chunkEvents.close();
63+
final cacheKey =
64+
'$userId${assetData.localId}${assetData.checksum}$width$height';
65+
final fileFromCache = await cache.getFileFromCache(cacheKey);
66+
if (fileFromCache != null) {
67+
try {
68+
final buffer =
69+
await ui.ImmutableBuffer.fromFilePath(fileFromCache.file.path);
70+
final codec = await decode(buffer);
71+
yield codec;
72+
return;
73+
} catch (error) {
74+
log.severe('Found thumbnail in cache, but loading it failed', error);
75+
}
76+
}
77+
78+
final thumbnailBytes = await assetData.local?.thumbnailDataWithSize(
79+
ThumbnailSize(width, height),
80+
quality: 80,
81+
);
82+
if (thumbnailBytes == null) {
6083
throw StateError(
61-
"Loading thumb for local photo ${asset.fileName} failed",
84+
"Loading thumb for local photo ${assetData.fileName} failed",
6285
);
6386
}
6487

65-
try {
66-
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
67-
final codec = await decode(buffer);
68-
yield codec;
69-
} finally {
70-
chunkEvents.close();
71-
}
88+
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbnailBytes);
89+
final codec = await decode(buffer);
90+
yield codec;
91+
await cache.putFile(cacheKey, thumbnailBytes);
7292
}
7393

7494
@override

mobile/lib/widgets/common/immich_thumbnail.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import 'dart:typed_data';
22

33
import 'package:flutter/material.dart';
4-
import 'package:flutter_hooks/flutter_hooks.dart';
4+
import 'package:hooks_riverpod/hooks_riverpod.dart';
55
import 'package:immich_mobile/providers/image/immich_local_thumbnail_provider.dart';
66
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
77
import 'package:immich_mobile/entities/asset.entity.dart';
88
import 'package:immich_mobile/utils/hooks/blurhash_hook.dart';
99
import 'package:immich_mobile/widgets/common/immich_image.dart';
1010
import 'package:immich_mobile/widgets/common/thumbhash_placeholder.dart';
1111
import 'package:octo_image/octo_image.dart';
12+
import 'package:immich_mobile/providers/user.provider.dart';
1213

13-
class ImmichThumbnail extends HookWidget {
14+
class ImmichThumbnail extends HookConsumerWidget {
1415
const ImmichThumbnail({
1516
this.asset,
1617
this.width = 250,
@@ -31,6 +32,7 @@ class ImmichThumbnail extends HookWidget {
3132
static ImageProvider imageProvider({
3233
Asset? asset,
3334
String? assetId,
35+
String? userId,
3436
int thumbnailSize = 256,
3537
}) {
3638
if (asset == null && assetId == null) {
@@ -48,6 +50,7 @@ class ImmichThumbnail extends HookWidget {
4850
asset: asset,
4951
height: thumbnailSize,
5052
width: thumbnailSize,
53+
userId: userId,
5154
);
5255
} else {
5356
return ImmichRemoteThumbnailProvider(
@@ -59,8 +62,10 @@ class ImmichThumbnail extends HookWidget {
5962
}
6063

6164
@override
62-
Widget build(BuildContext context) {
65+
Widget build(BuildContext context, WidgetRef ref) {
6366
Uint8List? blurhash = useBlurHashRef(asset).value;
67+
final userId = ref.watch(currentUserProvider)?.id;
68+
6469
if (asset == null) {
6570
return Container(
6671
color: Colors.grey,
@@ -79,6 +84,7 @@ class ImmichThumbnail extends HookWidget {
7984
octoSet: blurHashOrPlaceholder(blurhash),
8085
image: ImmichThumbnail.imageProvider(
8186
asset: asset,
87+
userId: userId,
8288
),
8389
width: width,
8490
height: height,

0 commit comments

Comments
 (0)