mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 04:42:02 +02:00
perf(theme): bound cover palette decoding and coalesce requests
This commit is contained in:
@@ -14,6 +14,7 @@ class CoverPalette {
|
||||
/// URL+brightness. Bounded because a long library-browsing session would
|
||||
/// otherwise keep every visited album's scheme alive.
|
||||
static final Map<String, ColorScheme> _cache = <String, ColorScheme>{};
|
||||
static final Map<String, Future<ColorScheme?>> _pending = {};
|
||||
static final List<String> _cacheOrder = <String>[];
|
||||
static const int _maxEntries = 32;
|
||||
|
||||
@@ -50,11 +51,23 @@ class CoverPalette {
|
||||
String source,
|
||||
Brightness brightness, {
|
||||
String? cacheKey,
|
||||
}) async {
|
||||
}) {
|
||||
final key = cacheKey ?? cacheKeyFor(source, brightness);
|
||||
final cached = _cache[key];
|
||||
if (cached != null) return cached;
|
||||
if (cached != null) return Future.value(cached);
|
||||
return _pending.putIfAbsent(
|
||||
key,
|
||||
() => _resolve(source, brightness, key).whenComplete(() {
|
||||
_pending.remove(key);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<ColorScheme?> _resolve(
|
||||
String source,
|
||||
Brightness brightness,
|
||||
String key,
|
||||
) async {
|
||||
final ImageProvider provider;
|
||||
if (_isNetworkSource(source)) {
|
||||
provider = cachedCoverImageProvider(source);
|
||||
@@ -66,7 +79,14 @@ class CoverPalette {
|
||||
|
||||
try {
|
||||
final scheme = await ColorScheme.fromImageProvider(
|
||||
provider: provider,
|
||||
// Palette extraction only samples a small image. Bound decoding too,
|
||||
// instead of decoding the original before Flutter downsamples it.
|
||||
provider: ResizeImage(
|
||||
provider,
|
||||
width: 112,
|
||||
height: 112,
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
brightness: brightness,
|
||||
);
|
||||
_cache[key] = scheme;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:spotiflac_android/theme/cover_palette.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('palette requests share a small aspect-preserving decode', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.runAsync(() async {
|
||||
final directory = await Directory.systemTemp.createTemp('palette-test-');
|
||||
final cache = PaintingBinding.instance.imageCache;
|
||||
cache.clear();
|
||||
cache.clearLiveImages();
|
||||
try {
|
||||
final recorder = ui.PictureRecorder();
|
||||
Canvas(recorder).drawRect(
|
||||
const Rect.fromLTWH(0, 0, 512, 256),
|
||||
Paint()..color = Colors.blue,
|
||||
);
|
||||
final picture = recorder.endRecording();
|
||||
final image = await picture.toImage(512, 256);
|
||||
final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
image.dispose();
|
||||
picture.dispose();
|
||||
final file = File('${directory.path}/cover.png');
|
||||
await file.writeAsBytes(bytes!.buffer.asUint8List());
|
||||
|
||||
final first = CoverPalette.resolve(file.path, Brightness.light);
|
||||
final second = CoverPalette.resolve(file.path, Brightness.light);
|
||||
expect(identical(first, second), isTrue);
|
||||
final scheme = await first;
|
||||
expect(scheme, isNotNull);
|
||||
// The original is 512 x 256. Its cached decode fits inside 112 x 112
|
||||
// while preserving the 2:1 aspect ratio, instead of retaining 512 KiB.
|
||||
expect(cache.currentSizeBytes, 112 * 56 * 4);
|
||||
expect(
|
||||
await CoverPalette.resolve(file.path, Brightness.light),
|
||||
same(scheme),
|
||||
);
|
||||
} finally {
|
||||
cache.clear();
|
||||
cache.clearLiveImages();
|
||||
await directory.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user