add support for avif images

Fixes #138
This commit is contained in:
Henry Hiles 2026-10-16 20:35:42 -04:00
commit 6d42477041
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
10 changed files with 219 additions and 8 deletions

View file

@ -1,6 +1,8 @@
import "dart:ui";
import "dart:io";
import "dart:ui" as ui;
import "package:flutter/widgets.dart";
import "package:flutter_avif/flutter_avif.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client.dart";
import "package:nexus/models/requests/download_media.dart";
@ -15,13 +17,23 @@ class MxcImage(final WidgetRef ref, final DownloadMediaRequest request)
ImageStreamCompleter loadImage(MxcImage key, ImageDecoderCallback decode) =>
MultiFrameImageStreamCompleter(codec: _loadAsync(decode), scale: 1.0);
Future<Codec> _loadAsync(ImageDecoderCallback decode) async {
Future<ui.Codec> _loadAsync(ImageDecoderCallback decode) async {
final file = await ref
.read(ClientController.provider.notifier)
.downloadMedia(request);
final buffer = await ImmutableBuffer.fromFilePath(file.path);
final bytes = await File(file.path).readAsBytes();
return decode(buffer);
// Flutter can't decode AVIF on Linux/Windows (or older Android/iOS), so we do it ourselves
final avifType = isAvifFile(bytes.sublist(0, 16.clamp(0, bytes.length)));
if (avifType != .unknown) {
final codec = avifType == .avif
? SingleFrameAvifCodec(bytes: bytes)
: MultiFrameAvifCodec(key: hashCode, avifBytes: bytes);
await codec.ready();
return AvifCodecAdapter(codec);
}
return decode(await ui.ImmutableBuffer.fromUint8List(bytes));
}
@override
@ -31,3 +43,25 @@ class MxcImage(final WidgetRef ref, final DownloadMediaRequest request)
@override
int get hashCode => request.hashCode;
}
class AvifCodecAdapter(final AvifCodec codec) implements ui.Codec {
@override
int get frameCount => codec.frameCount;
@override
int get repetitionCount => frameCount > 1 ? -1 : 0;
@override
Future<ui.FrameInfo> getNextFrame() async {
final frame = await codec.getNextFrame();
return AvifFrameInfo(frame.duration, frame.image);
}
@override
void dispose() => codec.dispose();
}
class AvifFrameInfo(
@override final Duration duration,
@override final ui.Image image,
) implements ui.FrameInfo;