diff --git a/mobile/ios/Runner/Images/ThumbnailsImpl.swift b/mobile/ios/Runner/Images/ThumbnailsImpl.swift index 14146b1c20..a919c4bf7c 100644 --- a/mobile/ios/Runner/Images/ThumbnailsImpl.swift +++ b/mobile/ios/Runner/Images/ThumbnailsImpl.swift @@ -20,6 +20,8 @@ class ThumbnailApiImpl: ThumbnailApi { return requestOptions }() private static let processingQueue = DispatchQueue(label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent) + private static let rgbColorSpace = CGColorSpaceCreateDeviceRGB() + private static let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue func setThumbnailToBuffer(pointer: Int64, assetId: String, width: Int64, height: Int64, completion: @escaping (Result) -> Void) { guard let bufferPointer = UnsafeMutableRawPointer(bitPattern: Int(pointer)) @@ -35,18 +37,16 @@ class ThumbnailApiImpl: ThumbnailApi { resultHandler: { (image, info) -> Void in guard let image = image, let cgImage = image.cgImage, - let dataProvider = cgImage.dataProvider, - let pixelData = dataProvider.data - else { completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil))); return } - - guard let sourceBuffer = CFDataGetBytePtr(pixelData) - else { completion(.failure(PigeonError(code: "", message: "Could not get pixel data buffer for \(assetId)", details: nil))); return } - let dataLength = CFDataGetLength(pixelData) - let bufferLength = width * height * 4 - guard dataLength <= bufferLength - else { completion(.failure(PigeonError(code: "", message: "Buffer is not large enough (\(bufferLength) vs \(dataLength) for \(assetId)", details: nil))); return } - - bufferPointer.copyMemory(from: sourceBuffer, byteCount: dataLength) + let context = CGContext( + data: bufferPointer, + width: cgImage.width, + height: cgImage.height, + bitsPerComponent: 8, + bytesPerRow: cgImage.width * 4, + space: Self.rgbColorSpace, + bitmapInfo: Self.bitmapInfo + ) else { completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil))); return } + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)) completion(.success(())) } ) diff --git a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart index 0a43730826..2bc9c1a1ec 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart @@ -92,7 +92,7 @@ class _ThumbnailState extends State { if (oldWidget.blurhash != widget.blurhash || oldWidget.localId != widget.localId || oldWidget.remoteId != widget.remoteId || - oldWidget.thumbhashOnly != widget.thumbhashOnly) { + oldWidget.thumbhashOnly && !widget.thumbhashOnly) { _decode(); } } @@ -106,7 +106,7 @@ class _ThumbnailState extends State { final blurhash = widget.blurhash; final imageFuture = thumbhashOnly ? Future.value(null) : _decodeFromFile(); - if (blurhash != null) { + if (blurhash != null && _image == null) { final image = thumbhash.thumbHashToRGBA(base64.decode(blurhash)); try { await _decodeThumbhash( diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index 3053480b7f..9e5dabcb01 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart @@ -6,6 +6,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; class ThumbnailTile extends ConsumerWidget { @@ -38,6 +39,8 @@ class ThumbnailTile extends ConsumerWidget { final isSelected = ref.watch( multiSelectProvider.select((multiselect) => multiselect.selectedAssets.contains(asset)), ); + final isScrubbing = + ref.watch(timelineStateProvider.select((state) => state.isScrubbing)); final borderStyle = lockSelection ? BoxDecoration( @@ -68,7 +71,12 @@ class ThumbnailTile extends ConsumerWidget { Positioned.fill( child: Hero( tag: '${asset?.heroTag ?? ''}_$heroIndex', - child: Thumbnail.fromBaseAsset(asset: asset, fit: fit, size: size), + child: Thumbnail.fromBaseAsset( + asset: asset, + fit: fit, + size: size, + thumbhashOnly: isScrubbing, + ), ), ), if (hasStack)