mirror of
https://github.com/immich-app/immich.git
synced 2025-07-31 15:08:44 -04:00
thumbhash render box
This commit is contained in:
parent
9dff520585
commit
2a1e914245
@ -26,7 +26,7 @@ const String kDownloadGroupLivePhoto = 'group_livephoto';
|
|||||||
|
|
||||||
// Timeline constants
|
// Timeline constants
|
||||||
const int kTimelineNoneSegmentSize = 120;
|
const int kTimelineNoneSegmentSize = 120;
|
||||||
const int kTimelineAssetLoadBatchSize = 256;
|
const int kTimelineAssetLoadBatchSize = 1024;
|
||||||
const int kTimelineAssetLoadOppositeSize = 64;
|
const int kTimelineAssetLoadOppositeSize = 64;
|
||||||
|
|
||||||
// Widget keys
|
// Widget keys
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:octo_image/octo_image.dart';
|
import 'package:octo_image/octo_image.dart';
|
||||||
@ -37,9 +36,7 @@ class Thumbnail extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OctoPlaceholderBuilder _blurHashPlaceholderBuilder(String? thumbHash, {BoxFit? fit}) {
|
OctoPlaceholderBuilder _blurHashPlaceholderBuilder(String? thumbHash, {BoxFit? fit}) {
|
||||||
return (context) => thumbHash == null
|
return (context) => Thumbhash(blurhash: thumbHash, fit: fit ?? BoxFit.cover);
|
||||||
? const ThumbnailPlaceholder()
|
|
||||||
: Thumbhash(blurhash: thumbHash, fit: fit ?? BoxFit.cover);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OctoErrorBuilder _blurHashErrorBuilder(String? blurhash, {BaseAsset? asset, ImageProvider? provider, BoxFit? fit}) =>
|
OctoErrorBuilder _blurHashErrorBuilder(String? blurhash, {BaseAsset? asset, ImageProvider? provider, BoxFit? fit}) =>
|
||||||
|
@ -91,12 +91,7 @@ class _BlurredBackdrop extends HookWidget {
|
|||||||
final blurhash = asset.thumbHash;
|
final blurhash = asset.thumbHash;
|
||||||
if (blurhash != null) {
|
if (blurhash != null) {
|
||||||
// Use a nice cheap blur hash image decoration
|
// Use a nice cheap blur hash image decoration
|
||||||
return Stack(
|
return Thumbhash(blurhash: blurhash, fit: BoxFit.cover);
|
||||||
children: [
|
|
||||||
const ColoredBox(color: Color.fromRGBO(0, 0, 0, 0.2)),
|
|
||||||
Thumbhash(blurhash: blurhash, fit: BoxFit.cover),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to using a more expensive image filtered
|
// Fall back to using a more expensive image filtered
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/transparent_image.dart';
|
|
||||||
|
|
||||||
class FadeInPlaceholderImage extends StatelessWidget {
|
|
||||||
final Widget placeholder;
|
|
||||||
final ImageProvider image;
|
|
||||||
final Duration duration;
|
|
||||||
final BoxFit fit;
|
|
||||||
|
|
||||||
const FadeInPlaceholderImage({
|
|
||||||
super.key,
|
|
||||||
required this.placeholder,
|
|
||||||
required this.image,
|
|
||||||
this.duration = const Duration(milliseconds: 100),
|
|
||||||
this.fit = BoxFit.cover,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox.expand(
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
placeholder,
|
|
||||||
FadeInImage(fadeInDuration: duration, image: image, fit: fit, placeholder: MemoryImage(kTransparentImage)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +1,194 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:thumbhash/thumbhash.dart' as thumbhash;
|
import 'package:thumbhash/thumbhash.dart' as thumbhash;
|
||||||
|
|
||||||
class Thumbhash extends StatelessWidget {
|
class ThumbhashImage extends RenderBox {
|
||||||
final String blurhash;
|
Color _placeholderColor;
|
||||||
|
ui.Image? _image;
|
||||||
|
BoxFit _fit;
|
||||||
|
|
||||||
|
ThumbhashImage({
|
||||||
|
required ui.Image? image,
|
||||||
|
required BoxFit fit,
|
||||||
|
required Color placeholderColor,
|
||||||
|
}) : _image = image,
|
||||||
|
_fit = fit,
|
||||||
|
_placeholderColor = placeholderColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
final image = _image;
|
||||||
|
final rect = offset & size;
|
||||||
|
if (image == null) {
|
||||||
|
final paint = Paint();
|
||||||
|
paint.color = _placeholderColor;
|
||||||
|
context.canvas.drawRect(rect, paint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
paintImage(
|
||||||
|
canvas: context.canvas,
|
||||||
|
rect: rect,
|
||||||
|
image: image,
|
||||||
|
fit: _fit,
|
||||||
|
filterQuality: FilterQuality.low,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performLayout() {
|
||||||
|
size = constraints.biggest;
|
||||||
|
}
|
||||||
|
|
||||||
|
set image(ui.Image? value) {
|
||||||
|
if (_image != value) {
|
||||||
|
_image = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set fit(BoxFit value) {
|
||||||
|
if (_fit != value) {
|
||||||
|
_fit = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set placeholderColor(Color value) {
|
||||||
|
if (_placeholderColor != value) {
|
||||||
|
_placeholderColor = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThumbhashLeaf extends LeafRenderObjectWidget {
|
||||||
|
final ui.Image? image;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
|
final Color placeholderColor;
|
||||||
|
|
||||||
|
const ThumbhashLeaf({
|
||||||
|
super.key,
|
||||||
|
required this.image,
|
||||||
|
required this.fit,
|
||||||
|
required this.placeholderColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
return ThumbhashImage(
|
||||||
|
image: image,
|
||||||
|
fit: fit,
|
||||||
|
placeholderColor: placeholderColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, ThumbhashImage renderObject) {
|
||||||
|
renderObject.fit = fit;
|
||||||
|
renderObject.image = image;
|
||||||
|
renderObject.placeholderColor = placeholderColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Thumbhash extends StatefulWidget {
|
||||||
|
final String? blurhash;
|
||||||
|
final BoxFit fit;
|
||||||
|
final Color placeholderColor;
|
||||||
|
|
||||||
const Thumbhash({
|
const Thumbhash({
|
||||||
required this.blurhash,
|
required this.blurhash,
|
||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
|
this.placeholderColor = const Color.fromRGBO(0, 0, 0, 0.2),
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Thumbhash> createState() => _ThumbhashState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ThumbhashState extends State<Thumbhash> {
|
||||||
|
String? blurhash;
|
||||||
|
BoxFit? fit;
|
||||||
|
ui.Image? _image;
|
||||||
|
Color? placeholderColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final blurhash_ = blurhash = widget.blurhash;
|
||||||
|
fit = widget.fit;
|
||||||
|
placeholderColor = widget.placeholderColor;
|
||||||
|
if (blurhash_ == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final image = thumbhash.thumbHashToRGBA(base64.decode(blurhash_));
|
||||||
|
_decode(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _decode(thumbhash.Image image) async {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final buffer = await ImmutableBuffer.fromUint8List(image.rgba);
|
||||||
|
if (!mounted) {
|
||||||
|
buffer.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final descriptor = ImageDescriptor.raw(
|
||||||
|
buffer,
|
||||||
|
width: image.width,
|
||||||
|
height: image.height,
|
||||||
|
pixelFormat: PixelFormat.rgba8888,
|
||||||
|
);
|
||||||
|
if (!mounted) {
|
||||||
|
buffer.dispose();
|
||||||
|
descriptor.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final codec = await descriptor.instantiateCodec(
|
||||||
|
targetWidth: image.width,
|
||||||
|
targetHeight: image.height,
|
||||||
|
);
|
||||||
|
if (!mounted) {
|
||||||
|
buffer.dispose();
|
||||||
|
descriptor.dispose();
|
||||||
|
codec.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final frame = (await codec.getNextFrame()).image;
|
||||||
|
buffer.dispose();
|
||||||
|
descriptor.dispose();
|
||||||
|
codec.dispose();
|
||||||
|
if (!mounted) {
|
||||||
|
frame.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_image = frame;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Image.memory(
|
return ThumbhashLeaf(
|
||||||
thumbhash.rgbaToBmp(thumbhash.thumbHashToRGBA(base64.decode(blurhash))),
|
image: _image,
|
||||||
fit: fit,
|
fit: fit!,
|
||||||
|
placeholderColor: placeholderColor!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_image?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
import 'package:immich_mobile/widgets/common/thumbhash.dart';
|
||||||
import 'package:octo_image/octo_image.dart';
|
import 'package:octo_image/octo_image.dart';
|
||||||
|
|
||||||
/// Simple set to show [OctoPlaceholder.circularProgressIndicator] as
|
/// Simple set to show [OctoPlaceholder.circularProgressIndicator] as
|
||||||
/// placeholder and [OctoError.icon] as error.
|
/// placeholder and [OctoError.icon] as error.
|
||||||
OctoSet blurHashOrPlaceholder(String? blurhash, {BoxFit? fit, Text? errorMessage}) {
|
OctoSet blurHashOrPlaceholder(String? blurhash, {BoxFit fit = BoxFit.cover, Text? errorMessage}) {
|
||||||
return OctoSet(
|
return OctoSet(
|
||||||
placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit),
|
placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit),
|
||||||
errorBuilder: blurHashErrorBuilder(blurhash, fit: fit, message: errorMessage),
|
errorBuilder: blurHashErrorBuilder(blurhash, fit: fit, message: errorMessage),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OctoPlaceholderBuilder blurHashPlaceholderBuilder(String? blurhash, {BoxFit? fit}) {
|
OctoPlaceholderBuilder blurHashPlaceholderBuilder(String? blurhash, {required BoxFit fit}) {
|
||||||
return (context) => blurhash == null
|
return (context) => Thumbhash(blurhash: blurhash, fit: fit);
|
||||||
? const ThumbnailPlaceholder()
|
|
||||||
: Thumbhash(
|
|
||||||
blurhash: blurhash,
|
|
||||||
fit: fit ?? BoxFit.cover,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OctoErrorBuilder blurHashErrorBuilder(
|
OctoErrorBuilder blurHashErrorBuilder(
|
||||||
String? blurhash, {
|
String? blurhash, {
|
||||||
BoxFit? fit,
|
BoxFit fit = BoxFit.cover,
|
||||||
Text? message,
|
Text? message,
|
||||||
IconData? icon,
|
IconData? icon,
|
||||||
Color? iconColor,
|
Color? iconColor,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user