draw to buffer

This commit is contained in:
mertalev 2025-07-21 14:34:09 +03:00
parent 91a5989929
commit a3fc5d80bb
No known key found for this signature in database
GPG Key ID: DF6ABC77AAD98C95
3 changed files with 23 additions and 15 deletions

View File

@ -20,6 +20,8 @@ class ThumbnailApiImpl: ThumbnailApi {
return requestOptions return requestOptions
}() }()
private static let processingQueue = DispatchQueue(label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent) 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, any Error>) -> Void) { func setThumbnailToBuffer(pointer: Int64, assetId: String, width: Int64, height: Int64, completion: @escaping (Result<Void, any Error>) -> Void) {
guard let bufferPointer = UnsafeMutableRawPointer(bitPattern: Int(pointer)) guard let bufferPointer = UnsafeMutableRawPointer(bitPattern: Int(pointer))
@ -35,18 +37,16 @@ class ThumbnailApiImpl: ThumbnailApi {
resultHandler: { (image, info) -> Void in resultHandler: { (image, info) -> Void in
guard let image = image, guard let image = image,
let cgImage = image.cgImage, let cgImage = image.cgImage,
let dataProvider = cgImage.dataProvider, let context = CGContext(
let pixelData = dataProvider.data data: bufferPointer,
else { completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil))); return } width: cgImage.width,
height: cgImage.height,
guard let sourceBuffer = CFDataGetBytePtr(pixelData) bitsPerComponent: 8,
else { completion(.failure(PigeonError(code: "", message: "Could not get pixel data buffer for \(assetId)", details: nil))); return } bytesPerRow: cgImage.width * 4,
let dataLength = CFDataGetLength(pixelData) space: Self.rgbColorSpace,
let bufferLength = width * height * 4 bitmapInfo: Self.bitmapInfo
guard dataLength <= bufferLength ) else { completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil))); return }
else { completion(.failure(PigeonError(code: "", message: "Buffer is not large enough (\(bufferLength) vs \(dataLength) for \(assetId)", details: nil))); return } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))
bufferPointer.copyMemory(from: sourceBuffer, byteCount: dataLength)
completion(.success(())) completion(.success(()))
} }
) )

View File

@ -92,7 +92,7 @@ class _ThumbnailState extends State<Thumbnail> {
if (oldWidget.blurhash != widget.blurhash || if (oldWidget.blurhash != widget.blurhash ||
oldWidget.localId != widget.localId || oldWidget.localId != widget.localId ||
oldWidget.remoteId != widget.remoteId || oldWidget.remoteId != widget.remoteId ||
oldWidget.thumbhashOnly != widget.thumbhashOnly) { oldWidget.thumbhashOnly && !widget.thumbhashOnly) {
_decode(); _decode();
} }
} }
@ -106,7 +106,7 @@ class _ThumbnailState extends State<Thumbnail> {
final blurhash = widget.blurhash; final blurhash = widget.blurhash;
final imageFuture = thumbhashOnly ? Future.value(null) : _decodeFromFile(); final imageFuture = thumbhashOnly ? Future.value(null) : _decodeFromFile();
if (blurhash != null) { if (blurhash != null && _image == null) {
final image = thumbhash.thumbHashToRGBA(base64.decode(blurhash)); final image = thumbhash.thumbHashToRGBA(base64.decode(blurhash));
try { try {
await _decodeThumbhash( await _decodeThumbhash(

View File

@ -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/duration_extensions.dart';
import 'package:immich_mobile/extensions/theme_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/images/thumbnail.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
class ThumbnailTile extends ConsumerWidget { class ThumbnailTile extends ConsumerWidget {
@ -38,6 +39,8 @@ class ThumbnailTile extends ConsumerWidget {
final isSelected = ref.watch( final isSelected = ref.watch(
multiSelectProvider.select((multiselect) => multiselect.selectedAssets.contains(asset)), multiSelectProvider.select((multiselect) => multiselect.selectedAssets.contains(asset)),
); );
final isScrubbing =
ref.watch(timelineStateProvider.select((state) => state.isScrubbing));
final borderStyle = lockSelection final borderStyle = lockSelection
? BoxDecoration( ? BoxDecoration(
@ -68,7 +71,12 @@ class ThumbnailTile extends ConsumerWidget {
Positioned.fill( Positioned.fill(
child: Hero( child: Hero(
tag: '${asset?.heroTag ?? ''}_$heroIndex', 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) if (hasStack)