Skip to content

Generate image thumbnails and use thumbnails in the media gallery#1568

Open
lissine0 wants to merge 5 commits intomonal-im:developfrom
lissine0:image-thumbnails
Open

Generate image thumbnails and use thumbnails in the media gallery#1568
lissine0 wants to merge 5 commits intomonal-im:developfrom
lissine0:image-thumbnails

Conversation

@lissine0
Copy link
Member

@lissine0 lissine0 commented Feb 9, 2026

  • Generate thumbnails for image attachments, including for SVGs
  • Use the image thumbnails in the ChatView. This should make the loading of images much faster. I also used HEIC or JPEG instead of PNG for the thumbnails, to make the size smaller and reduce the loading times even more.
  • Use the generated thumbnails of videos and images in the media gallery. This has a noticeable improvement on the loading time, and also fixes some pre-existing issues (like thumbnails for videos and for SVGs which previously didn't work)

A few notes:

  • ExyteChat doesn't already have support displaying SVGs, so we don't have to use their SVG library (the original argument for using it being that it's already in our dependency tree.)
    We may want to consider this as the Exyte library has poor SVG support. In fact doesn't even render the recently added Monal SVG logos correctly.
  • GIFs (and other animated image formats like WebP I presume) are displayed as still images.
    The removed Giphy library isn't useful in this context, as I'd imagine it works by just sending an image link from one phone to another, and the receiving end would download the image from the Giphy server. i.e. it wouldn't handle animated images sent from a non-giphy client like Gajim.
    Therefore, if we want to display animated images as animated we'll have to patch our exyteChat fork and add another library. Note that FLAnimatedImage (used in the old chatView for GIF displaying) doesn't support WebP.
    (Animated image support is out of scope of this PR, as it needs to be done in the exytechat fork anyway. I just wanted to discuss it.)

return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
AnyPromise* imagePromise = nil;
if(fileInfo.isSVGImage)
imagePromise = [HelperTools renderUIImageFromSVGURL:fileInfo.fileURL];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing Monal code often uses PMKHang when loading SVGs. What does that do and should I use it here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since ObjC doesn't have async/await, I'm using PMKHang to hang the current thread until the given promise is resolved. It should obviously only be used in a thread that is allowed to hang (never in the main thread for example).

See the implementation here: https://github.com/mxcl/PromiseKit/blob/2bc44395edb4f8391902a9ff7c220471882a4d07/Sources/hang.m#L10

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah wait, it can even be used in the main thread, because it spins the runloop while "hanging".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the ObjC pendant of the async/await implementation for promises over here: https://github.com/mxcl/PromiseKit/blob/2bc44395edb4f8391902a9ff7c220471882a4d07/Sources/Async.swift

Copy link
Member

@tmolitor-stud-tu tmolitor-stud-tu Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, another note: the swift version of "hang" says in the comments:

// hang doesn't make sense on threads that aren't the main thread.
// use `.wait()` on those threads.

So the swift version and the ObjC versions seem to be slightly different.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your question now: No, you don't need to use PMKHang here. It should only be used if you need the result of the svg promise in the following synchronous code (like in the notification manager) and it is okay to hang the current thread while waiting for the svg promise (the notification manager will hang the receive queue which is okay).

And some cleanups:
- Use more concise code, now that we have thumbnails for both images and videos
- Use the isAudio, isVideo and isImage MLFiletransferInfo properties
- Use the cacheId directly as attachment id, rather than creating a UUID from it
unreachable(@"Trying to generate a thumbnail for a file that is neither an image nor a video");
}

+(AnyPromise*) generateThumbnailFromImageFile:(MLFiletransferInfo*) fileInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to move this method to MLFiletransferInfo? Maybe even make it a readonly property (hanging for the result rather than returning a promise).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even better: remove it and use the HelperTools method addUploadItemPreviewForItem: as stated in a comment further down.

else if(fileInfo.isVideo)
return [HelperTools generateThumbnailFromVideoFile:fileInfo.cacheFilePath havingMimeType:fileInfo.mimeType andFileExtension:fileInfo.fileExtension];
else
unreachable(@"Trying to generate a thumbnail for a file that is neither an image nor a video");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just return a SFSymbol image (document/file image) and use this method for every filetransfer.

The HelperTools method for handling a file shared with Monal does even have some thumbnail generating code that could be reused here, so every location showing some thumbnail would show the same thumbnail. That would be the best solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be the HelperTools method addUploadItemPreviewForItem:, see my other comments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of addUploadItemPreviewForItem. I intentionally didn't use it, for future-proofing purposes:
If we use it now to generate generic thumbnails for all kinds of files, in the future we may start to generate actual thumbnails for PDFs for example. But then pre-existing PDFs would keep showing the generic thumbnails that were already generated in the past. (because we cache the thumbnails on disk)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My plan was actually to get rid of addUploadItemPreviewForItem: after the old chat view is deleted.
That method for example generates video thumbnails correctly on macOS, but returns a generic one when selecting a video for upload on iOS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could easily clear the disk cache once we add new thumbnail types (e.g. PDF previews like in your example above).

Returning only a generic icon for videos on iOS could be fixed, I guess?

}

+(AnyPromise*) generateVideoThumbnailFromFile:(NSString*) file havingMimeType:(NSString*) mimeType andFileExtension:(NSString* _Nullable) fileExtension
+(AnyPromise*) generateThumbnailFromFile:(MLFiletransferInfo*) fileInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to move this method to MLFiletransferInfo? Maybe even make it a readonly property (hanging for the result rather than returning a promise).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can move this to MLFiletransferInfo, but I don't see the point in making it a readonly property.
The thumbnailURL property is already readonly, with automatic updates in the UI, and disk / memory cache. Its setter calls this method (generateThumbnailFromFile) if a thumbnail wasn't generated and cached previously.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, makes sense, I guess.


-(NSURL*) thumbnailURL {
if(!self.isImage && !self.isVideo)
return nil;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe always return a thumbnail and reuse the thumbnail generating code from the HelperTools method addUploadItemPreviewForItem: (see my other comment above)?

@tmolitor-stud-tu
Copy link
Member

Regarding the SVG library choice: sure, lets switch to a better lib. Do you know of any (should ideally be lightweight).

@tmolitor-stud-tu
Copy link
Member

Yeah, Giphy is just a library of gif images / (sometimes) animated images, no rendering library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments