mirror of
https://github.com/immich-app/immich.git
synced 2026-07-25 14:00:45 +03:00
mobile: thumbnails load feels slow, possibly due to extra thumbnail generation step #4451
Closed
opened 2026-02-05 10:29:47 +03:00 by OVERLORD
·
12 comments
No Branch/Tag Specified
main
chore/translations
fix/partner-ownership-contention
renovate/machine-learning
renovate/pypi-pillow-vulnerability
renovate/pypi-pydantic-settings-vulnerability
renovate/java-25.x
renovate/mobile
chore/harden-mobile-openapi-gen
renovate/java-21.x
fix/min-faces
refactor/asset-update
feat/merge-people-mobile
chore/face-cluster-abstraction
refactor/search-v3/pr-2a
fcast
fix/android-local-album-date
fix/24545-sync-retry-cx
fix/android-locked-folder-delete
make-sure-memory-widget-render
chore/freezed-example
fix/clamp-local-date-time-to-valid-value
fix/datetime-max-limit
fix/viewer-long-image-aspect
feat/native-core
feat/yucca-integration
refactor/mobile-actions
fix/ws-reconnect-battery
renovate/sharp
renovate/pigeon-27.x
refactor/auto-trash-sync
feat/release-source-branch
refactor/session-repository
fix/library-map-live-bounds-updates
renovate/permission_handler-12.x
renovate/node-24.x
fix/mobile-scroll-velocity-placeholders
fix/library-map-loading-flash
agg23/appbar-experiments
renovate/pep621-lock-file-maintenance-machine-learning
refactor/undo-actions
refactor/actions
feat/restore-action
feat/delete-action
fix/29853-thumbnail-decode-size
bugfix/live-photo-stuck
feature/load-previews
chore/pub-upgrade
custom-app-logo
fix/failed-sync-blocks-resume
feat/ml-hwaccel-testing
feat/workflow-logs
feat/server-chunked-uploads
feat/upload-action
fix-29727
feat/download-tag-action
feat/share-action
feat/album-actions
fix/android-cr3-save
feat/browser-profile-action
feat/cast-slideshow-action
feat/locked-folder-action
fix/unauthorized-memory-creation
feat/context-actions
refactor/action-class
feat/memories-view
refactor/asset-action-filter
feat/archive-action
feat/stack-action
feat/mobile-hls-player
refactor/stack-actions
feat/workflow-asset-tags
fix/timeline-drag-select-stops
fix/featured-photo-thumbnail-cache
fix/album-timeline-date-ordering
chore/album-remove-assets
feat/partner-table-permissions
fix/ocr-after-edit
fix/re-enable-pruning
refactor/delete-restore-actions
apk-build-deployment
edit-photo-stacking
fix/mise-lockfile
feat/web-hls-hover
refactor/store-cleanup
fix/hash-mismatch
refactor/app-metadata
refactor/advanced-settings
refactor/current-user
fix/dedupe-integrity-missing-paths
chore/albums-select
fix/fdroid-meta
refactor/per-asset-backup-flag
fix/av1-threads
fix/pass-secrets
fix/download-progress-stuck
fix/negative-backup-remainder
feat/map-asset-number
test-bump-prerelease
fix/handle-unlinking-motion-photos
fix/date-range-formatting
fix/mobile-album-action-msg
asset-add-to-album-trigger
refactor/face-editor-decoupling
feat/zoom-aware-face-editing
refactor/adaptive-image-dimensions
fix-face-editor-video-preview
fix-stack-face-tag-selection
fix/face-boxes-edit-panel
workflow-webhook-step
feat/hero_view_transitions
fix/map-sidepanel-queries
feat/dart-openapi-generator
claude/share-quality-5d7Gn
drag-drop-ghost-target
refactor/workflow-page
refactor/mobile-upload
fix-scroll-flicker-2
update-mise-lock-file
feat/kysely-0.29
release-candidate-flow-1
fix/mobile-22522-sqlite-2067
fix/job-lock
release-candidate-flow-3
release-candidate-flow-2
show-in-timeline-toggle
fix/mise-windows
plan-local-image-display
fix/remote-thumbnail-fallback
chore/riverpod-update
album-order-per-user
fix/web-timeline-dom-node-retention
chore/svelte-enable-state-referenced-locally
feat/favorite-albums
feat/patch-release-from-branch
fix/oauth-linking
feat/timeline_scroll_buffer
panorama-face-overlay
push-txxyuusptoru
push-zpwsovysllvn
chore/queue-endpoint-migration
fix/remove-from-album
chore/thumbnail-histogram
fix/video-thumbnail-bindthis-regression
fix/scrubber-drag-edge-cases
fix/timeline_layout_row_height
refactor-cancellable-task
uhthomas/fix-mobile-use-timer
uhthomas/fix-mobile-video-controls-key
uhthomas/chore-mobile-search-declarative
owner-role-album-user
feat/livephoto-editing
favorite-albums
uhthomas/fix-mobile-isolate-auth
fix/library-watcher-excludes
chore/library-e2e-to-medium
push-ynllunsvrpxy
fix/dont-delete-offline-library-files
push-zvwwxmvkrspv
push-kywsnvvkwzts
push-xxnxokokzrou
push-voozlrlkxsnu
view-in-timeline-transition
push-uymvmlknxpmx
push-qslzsplnusoz
fix/mobile-video-stall
uhthomas/draft-mobile-image-cache-cancel
push-yquostpqnkxp
feat/vitest-4
claude/offload-icloud-hashing-bI3GZ
midzelis/wip
uhthomas/fix-server-fsync-all
feat/mobile-widget-shared-client
push-lnyuzsutzkqq
push-vqqqxxvkunru
push-wvnmqoswptww
push-nwxlpmyzkyrl
fix/map-webgl-error
fix/mobile-player-fit
feat/panorama-tiles
csp-policy
push-rsywxvptwxuv
refactor/restores-file-interceptor
postgres-socketio
claude/auto-screenshot-web-changes-Y7efI
visual-review/pr-26535
push-lvyturrtwkrq
feat/library-offline-count
fix/mobile-video-aspect-ratio
uhthomas/fix-mobile-search-results
uhthomas/feat-sort-smart-search
uhthomas/chore-mobile-maplibre
uhthomas/mobile-fix-asset-details-album-pop
feat/crawl-wrapper
push-skvzqoozqkpl
feat/edit-filters
feat/pg-queue
refactor/asset-upload
better-project-structure
uhthomas/mobile-feat-asset-viewer-details
fix/ml-rocm-build
feat/asset-file-apis
feature/bottom-buttons-order
sqlite_thumbs
fix-keep-correct-ios-shared-album-asset
push-vpxwmwwxwnvw
fix-migration-width-height
shared-deep-link-handler
feat/thumbnail-native-clients
feat/platform-clients
fix/foreground-cloud-sync
filter-by-person
refactor/sidebar
fix/merged-edited-assets
feat/create-job-with-dto
feat/ios-fastlane-match
match-signing
fix-update-time-update-timeline
feat/modal-routes
feature/mobile-view-asset-owner
feat/system-settings
feature/show-activity-count
feat/location-favorites
feature/rearrange-buttons-2
fix/download-storage-template
chore/originals-in-asset-files
ben/tree-a11y
new-search-filter-ui
refactor/expectSelectedReadonly
refactor/mobile-grdb
feat/mobile-native-local-sync
refactor/timeline_ops
fix/scrubber_end
refactor/virtualsegment
refactor/rename_daymonth_groups
fix-remote-sync-clean-up
debug/cf-chunked-uploads
feat/search-filter-album/web
feat/session-permissions
feat/mobile-dynamic-thumbnails
refactor/extract_photostream
refactor/rename_load_api
refactor/timeline2
refactor/timeline3
feat-no-thumbhash-cache
feat/mobile-hdr-images
feat/beta-background-upload
fix/beta-timeline-memories-setting
fix/failed-uploads-not-removed
feat/groups
drift-map-page
feat/add-to-album-action
track-livephotos
feat/maintenance-worker
refactor/server-side-dedupe
feat/integrity-checks
dev/recognition-eval
lighter_buckets_test
perf/postgres-queue
tmp/demo-snapshot-preview
fix/server-migration-file-extension
rknn-toolkit-lite2
feature/Add-rocm-support-for-machine-learning
chore/async-hash-file
feat/shared-link-view-count
feat/graphql
no-video-player
fix/server-qsv-output-format
chore/server-geodata-tweaks
mobile/native-video-player-no-hero
feat/local-tileserver
feat/ml-armnn-conversion
chore/handle-output_dims
feat/capacitor-mobile-app-poc
feat/server-nvenc-hw-decoding
web/automation-ui
object-storage
ml/tflite
feat/ml-export-cli
v3.0.3
v3.0.2
v3.0.1
v3.0.0
v3.0.0-rc.4
v3.0.0-rc.3
v3.0.0-rc.2
v3.0.0-rc.1
v3.0.0-rc.0
v2.7.5
v2.7.4
v2.7.3
v2.7.2
v2.7.1
v2.7.0
v2.6.3
v2.6.2
v2.6.1
v2.6.0
v2.5.6
v2.5.5
v2.5.4
v2.5.3
v2.5.2
v2.5.1
v2.5.0
v2.4.1
v2.4.0
v2.3.1
v2.3.0
v2.2.3
v2.2.2
v2.2.1
v2.2.0
v2.1.0
v2.0.1
v2.0.0
v1.144.1
v1.144.0
v1.143.1
v1.143.0
v1.142.1
v1.142.0
v1.141.1
v1.141.0
v1.140.1
v1.140.0
v1.139.4
v1.139.3
v1.139.2
v1.139.1
v1.139.0
v1.138.1
v1.138.0
v1.137.3
v1.137.2
v1.137.1
v1.137.0
v1.136.0
v1.135.3
v1.135.2
v1.135.1
v1.135.0
v1.134.0
v1.133.1
v1.133.0
v1.132.3
v1.132.2
v1.132.1
v1.132.0
v1.131.3
v1.131.2
v1.131.1
v1.131.0
v1.130.3
v1.130.2
v1.130.1
v1.130.0
v1.129.0
v1.128.0
v1.127.0
v1.126.1
v1.126.0
v1.125.7
v1.125.6
v1.125.5
v1.125.4
v1.125.3
v1.125.2
v1.125.1
v1.125.0
v1.124.2
v1.124.1
v1.124.0
v1.123.0
v1.122.3
v1.122.2
v1.122.1
v1.122.0
v1.121.0
v1.120.2
v1.120.1
v1.120.0
v1.119.1
v1.119.0
v1.118.2
v1.118.1
v1.118.0
v1.117.0
v1.116.2
v1.116.1
v1.116.0
v1.115.0
v1.114.0
v1.113.1
v1.113.0
v1.112.1
v1.112.0
v1.111.0
v1.110.0
v1.109.2
v1.109.1
v1.109.0
v1.108.0
v1.107.2
v1.107.1
v1.107.0
v1.106.4
v1.106.3
v1.106.2
v1.106.1
v1.106.0
v1.105.1
v1.105.0
v1.104.0
v1.103.1
v1.103.0
v1.102.3
v1.102.2
v1.102.1
v1.102.0
v1.101.0
v1.100.0
v1.99.0
v1.98.2
v1.98.1
v1.98.0
v1.97.0
v1.96.0
v1.95.1
v1.95.0
v1.94.1
v1.94.0
v1.93.3
v1.93.2
v1.93.1
v1.93.0
v1.92.1
v1.92.0
v1.91.4
v1.91.3
v1.91.2
v1.91.1
v1.91.0
v1.90.2
v1.90.1
v1.90.0
v1.89.0
v1.88.2
v1.88.1
v1.88.0
v1.87.0
v1.86.0
v1.85.0
v1.84.0
v1.83.0
v1.82.1
v1.82.0
v1.81.1
v1.81.0
v1.80.0
v1.79.1
v1.79.0
v1.78.1
v1.78.0
v1.77.0
v1.76.1
v1.76.0
v1.75.2
v1.75.1
v1.75.0
v1.74.0
v1.73.0
v1.72.2
v1.72.1
v1.72.0
v1.71.0
v1.70.0
v1.69.0
v1.68.0
v1.67.2
v1.67.1
v1.67.0
v1.66.1
v1.66.0
v1.65.0
v1.64.0
v1.63.2
v1.63.1
v1.63.0
v1.62.1
v1.62.0
v1.61.0
v1.60.0
v1.59.1
v1.59.0
v1.58.0
v1.57.1
v1.57.0
v1.56.2
v1.56.1
v1.56.0
v1.55.1
v1.55.0
v1.54.1
v1.54.0
v1.53.0
v1.52.1
v1.52.0
v1.51.2
v1.51.1
v1.51.0
v1.50.1
v1.50.0
v1.49.0
v1.48.1
v1.48.0
v1.47.3
v1.47.2
v1.47.1
v1.47.0
v1.46.1
v1.46.0
v1.45.0
v1.44.0
v1.43.1
v1.43.0
v1.42.0_65-dev
v1.41.1_64-dev
v1.41.0_64-dev
v1.40.1_63-dev
v1.40.0_63-dev
v1.39.0_61-dev
v1.38.2_60-dev
v1.38.1_60-dev
v1.38.0_60-dev
v1.37.0_58-dev
v1.36.2_56-dev
v1.36.1_55-dev
v1.36.0_55-dev
v1.35.0_54-dev
v1.34.0_53-dev
v1.33.1_52-dev
v1.33.0_52-dev
v1.32.1_51-dev
v1.32.0_50-dev
v1.31.1_49-dev
v1.31.0_49-dev
v1.30.2_48-dev
v1.30.0_46-dev
v1.29.6_45-dev
v1.29.6_44-dev
v1.29.5_44-dev
v1.29.4_44-dev
v1.29.3_43-dev
v1.29.2_43-dev
v1.29.1_43-dev
v1.29.0_42-dev
v1.28.4_41-dev
v1.28.4_42-dev
v1.28.3_41-dev
v1.28.2_40-dev
v1.28.1_39-dev
v1.28.0_38-dev
v1.27.0_37-dev
v1.26.0_36-dev
v1.25.0_35-dev
v1.24.0_34-dev
v1.23.0_33-dev
v1.22.0_32-dev
v1.21.1_31-dev
v1.21.0_31-dev
v1.20.3_30-dev
v1.20.2_30-dev
v1.20.1_30-dev
v1.20.0_30-dev
v1.19.1_29-dev
v1.19.0_29-dev
v1.18.0_27-dev
v1.17.0_25-dev
v1.16.0_23-dev
v1.15.1_21-dev
v1.15.0_21-dev
v1.14.0_21-dev
v1.13.0_20-dev
v1.12.0_18-dev
v1.11.0_17-dev
v1.10.0_15-dev
v1.9.1_14-dev
v1.9.0_13-dev
v1.8.0_12-dev
v1.7.0_11-dev
v1.6.0_10-dev
v1.5.1+9-dev
v1.5.0+8-dev
v1.4.0+7-dev
v1.4.0+6-dev
v1.4.0-dev
v1.3.0-dev
v1.3.1-dev
v0.6-dev
v0.5-dev
v0.4-dev
v0.3-dev
v0.2-dev
first-android-release
Labels
Clear labels
accessibility
changelog:enhancement
changelog:security
changelog:skip
changelog:translation
cli
date-time
dependencies
documentation
external-library
format
good first issue
mobile-beta
mobile-beta
mobile-beta
needs-answer
nice to have
pull-request
sharing
tech-debt
📱mobile
🖥️web
🗄️server
🧠machine-learning
Mirrored from GitHub Pull Request
No Label
📱mobile
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: immich-app/immich#4451
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @Atemu on GitHub (Oct 2, 2024).
The bug
Ever since I've started using Immich, I noticed that thumbnails feel quite slow to load to a usable resolution. Setting the option to always load remote thumbnails works around this issue causing thumbnails to load reasonably quick but that has obvious issues.
This phone is a Fairphone 4 which is by no means a particularly fast phone but it certainly isn't a slow phone either. I remember that Google photos never used to have issues like this on the much slower Oneplus 5 I used to use but I don't know how it fares on the FP4 since I stopped using Google photos years ago.
For reference, the Fossify gallery has no such issues on the same phone with the same pictures. Thumbnails take a short moment to appear but stay the background colour in the short moment until they're there. It's really quite quick though, nothing I'd be annoyed about or really notice unless I looked for it.
Not so much with Immich though unfortunately.
A thing I've noticed in this regard is that Immich has another step in the process: it loads a noticeably low-resolution thumbnail first before loading the actual thumbnail.
This might honestly be just a feeling thing where the jump between placeholder to something appearing does not result in the actual usable image to appear. Instead, a super low resolution placeholder appears and it's so low res that you can't tell what's in it in the short moment it takes to load the actual thumbnail that is high resolution enough to tell what's in the picture.
All it really does is delay the actual thumbnail from appearing and I never had a use for it. I honestly think it'd be better to skip this generation step entirely.
This is how it works when forcing remote thumbnails too and that's with the much higher latencies and lower bandwidth of a wireless connection to a network-local server. This alone might be why that feels so much better.
Would it be possible to make generating this middle step thumbnail at least optional?
Another thought that crossed my mind while writing this: The local image has to be read from disk, decoded, downscaled, encoded again and written somewhere to generate the thumbnail.
This generation step obviously takes a noticeable amount of time. Wouldn't it be better to just skip it and show the image directly, in full resolution? I don't know how feasible this is but, in theory, merely decoding the image and scaling the resulting bitmap should be rather cheap as both of those tasks can be HW-accelerated via the video codec HW (mjpeg/h.265) and generic GPU 2D accel respectively. Perhaps worth checking out as that'd save us from generating thumbnails for local files entirely.
If it turns the view too sluggish but is still faster to load than generating thumbnails, Immich could perhaps show the originals until the thumbnails are generated asynchronously and seamlessly swap those in as they finish generating.
The OS that Immich Server is running on
NixOS
Version of Immich Server
v1.116.2
Version of Immich Mobile App
v1.116.2
Platform with the issue
Your docker-compose.yml content
Your .env content
Reproduction steps
Relevant log output
No response
Additional information
(Not yet using the NixOS module, still plain docker-compose.)
@bo0tzz commented on GitHub (Oct 2, 2024):
I think this is a dupe of #2128, right?
Immich doesn't generate thumbnails on demand, they're created ahead of time so there's no generation step slowing things down here.
@Atemu commented on GitHub (Oct 2, 2024):
Hm, no that's not quite what I'm experiencing. It doesn't cause lag or anything or takes extremely long, it just takes noticeably longer to load the thumbnails in after starting to load them than a standard gallery app and has this weird in-between step of a super low res thumbnail that does, to me, does not appear to be helpful.
When exactly does the Immich app pre-generate the thumbnails?
Also surely Immich can't pre-generate the thumbnails of all images you could possibly browse ahead of time as those can range into the 10000s, so there must be a limitation on how much and when thumbnails are generated, right?
@mertalev commented on GitHub (Oct 2, 2024):
Not a mobile dev, but I don't think the app persists the thumbnails it generates. There's a low-res thumbnail that it makes and switches with a high-res thumbnail, and in-memory caches that store the raw decoded data. I don't think there's an encoding step.
Looking at the code here, it seems like it's effectively reading and decoding from disk twice to make those two thumbnails. There's almost no time saved by resizing to
32, 32vs.width, height, so the low-res thumbnail seems kind of pointless. I guess it makes better use of the in-memory cache since more thumbnails can fit, but unless the cache hit rate is high it's probably slower than just skipping the low-res thumbnail.@Atemu commented on GitHub (Oct 2, 2024):
Oh interesting, I had assumed it does. Perhaps that would be another optimisation.
Although I believe Android provides a thumbnail caching mechanism of its own for local images if I'm not mistaken. That should probably just be used if possible.
Ah thank you, I had attempted to take a look around myself.
I had a look at what I believe to be the source for
thumbnailDataWithSize()and it takes the codec aswell as the quality as parameters. We're calling it with the default of jpeg encoder and set quality to 75, so I assume it's encoding to JPEG.If we truly never cache these thumbnails anywhere, we shouldn't be encoding anything and should just scale down the bitmap of the asset. That should be much faster than encoding two JPEGs.
@mertalev commented on GitHub (Oct 2, 2024):
Oh, you're right. It's encoding two JPEGs, then decoding them back...
@edbr-xyz commented on GitHub (Oct 8, 2024):
I also have this issue on my OnePlus 7 Pro.
I notice that images that are only remote (were uploaded from my PC), have nice looking thumbnails that seem to load almost instantly, even while images that are on the device, above and below, are still just 32x32 blurry blocks.
If I then close the app and turn off my WiFi and data, then re-open Immich, the thumbnails of those remote images are still there, and still load instantly.
@Atemu commented on GitHub (Oct 9, 2024):
What I experience doesn't persist long enough to take a screenshot. What you experience might actually be https://github.com/immich-app/immich/issues/2128.
The root of that issue could be similar though.
@edbr-xyz commented on GitHub (Oct 11, 2024):
That's probably a factor, but Immich could be caching thumbnails for local images, but instead it seems to only do it for the remote ones.
@EinToni commented on GitHub (Apr 17, 2025):
Hey, may I chip in here? :)
I looked into it and did a performance profiling of this part of the app and it is actually not the decoding that takes so much time, it is the pure loading of the image from disk and rescaling it in
asset.local?.thumbnailDataWithSize(...).I did a bit more testing and this is the result of the time it takes to execute
thumbnailDataWithSizewith different options:( I highlighted the currently used configurations)
The rest of the _codec method for local thumbnails uses no measurable time.
I did these tests on my pixel 8 running in profiling mode, so quite realistically results.
I would propose to drop the small thumbnail. This is just costing valuable time. The user is waiting ~0.25s to get from the blur to the still bad looking small thumbnail. Only then after another ~0.4s the user gets the actual thumbnail. That is 0.65s on average in total!!
When dropping the small thumbnail the user would only have to wait for ~0.4s to get from the blur straight to the good thumbnail. I think this is way better for the user experience, because the small thumbnail is (in my opinion) not helpful. It is rather just frustrating, because the image appeared to have loaded (the visual move from blur to something better) but it's still so bad that it is basically a blur.
Furthermore the thumbnails are not cached and therefore always recalculated. I would also propose to implement caching here as equally done for the remote thumbnails. The already existent
ThumbnailImageCacheManagercan be reused for that.As I have already implemented it for testing purposes I would open a PR in the next days (after a bit of cleanup) if this sounds like an acceptable solution.
@mertalev commented on GitHub (Apr 17, 2025):
I agree with removing the 32x32 thumbnail, but I don't think this is quite accurate.
thumbnailDataWithSizeis decoding the original image first before encoding it into a small image. If you just tested the time that method takes, you're measuring loading from disk + decoding + resizing + encoding. Also, the timing numbers you shared aren't necessarily representative of what it would be without the 32x32 thumbnail. The second thumbnail is once again loading the original image right afterwards, so it benefits from better caching in the storage layer.It's also interesting to see that the quality has such a big impact on the timing. JPEG encoding time doesn't generally change much with different Q values, so I wonder what's causing that difference. It's probably worth looking into.
Caching should definitely help as well.
@EinToni commented on GitHub (Apr 18, 2025):
Thank you for the input.
True, the measurement is not accurate, but I focused more on the real application than on theoretical measurements and wanted to show a relative difference of the different setting... Although I have to confess I made a blunder. I completely neglected the caching of android itself and was with my head just thinking about explicit implemented caching. The measurements I did were all in one row, the strong decrease in time usage from the top to bottom is therefore mainly from caching.
I redid some tests but this time just took the time for all thumbnails to finish generating. Removing the small thumbnail still speed things up, calculating the 77 thumbnails with:
As you already noted, they are not accurate and just give a rough estimate of the changes. But it is still very noticeable when using it. I used this changed version to browse through my local images and it felt a lot snappier.
@mertalev commented on GitHub (Apr 18, 2025):
Thanks for retesting! 1550ms vs 2500ms is a huge difference.