feat(rotate): turn a JPEG from the viewer and keep the turn - #49
Merged
Merged
Conversation
A rotate button ahead of the edit button turns the picture on screen at once, and the turn is written to the file shortly after by rewriting its Exif orientation tag. Nothing is decoded and nothing is re-encoded, so the picture is the same picture however many times it is turned, and the tag is what the preview generator reads, so the new framing follows the file into the Files grid, Photos and the mobile clients. Offered only for a JPEG the user may write. No other format carries an orientation that both the browser and the preview generator honour, and a turn shown on a file that cannot keep it is worse than no button. Written once the user stops rather than once per click: every write makes a version of the file, so a turn per click would leave four copies of a photo in the history on the way round a full circle. A picture turned the whole way round is not written at all. The fit is worked out against the box the picture occupies on screen rather than its own, or a landscape photo turned on its side would be taller than the frame and spill over the chrome above and below. Zoom and pan are measured against the same box. Co-authored-by: vladopol <vladopol@gmail.com> Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #49 +/- ##
==========================================
+ Coverage 88.00% 88.10% +0.10%
==========================================
Files 36 37 +1
Lines 2510 2624 +114
Branches 525 558 +33
==========================================
+ Hits 2209 2312 +103
- Misses 297 305 +8
- Partials 4 7 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A rotate button ahead of the edit button turns the picture on screen at once, and the turn is written to the file about a second later by rewriting its Exif orientation tag. Nothing is decoded and nothing is re-encoded, so the picture is the same picture however many times it is turned, and the scan is copied byte for byte.
That tag is what
fixOrientation()reads during preview generation (lib/private/Preview/Image.php:42in server), and a write clears the cached previews (lib/private/Preview/Watcher.php:37), so one PUT carries the new framing into the Files grid, Photos and the mobile clients rather than leaving it a viewer-only illusion.Relates to nextcloud/viewer#2804, and picks up nextcloud/viewer#3190 by @vladopol, who proposed this in June and waited three and a half months on a repo we then froze. Their PR is against a branch that can no longer take it, and its rotation is visual only, but the idea and the shape of the toolbar change are theirs, so the commit carries their
Co-authored-by.JPEG only, and only where the user may write. No other format carries an orientation the stack honours:
lib/private/Image.php:413returns -1 for anything that is notIMAGETYPE_JPEG, so a PNG or WebP would need a hard rotation and a re-encode. That is written up with measurements in #47. The button is hidden rather than shown-and-failing, because a turn that silently does not stick is worse than no turn.Written once the user settles, not once per click.
apps/files_versions/lib/Storage.php:163creates a version on every write with no interval guard, so four clicks would put four copies of a 7MB photo in the history on the way round a full circle. The composable waits 1.2s after the last turn, flushes on navigate and on close, and writes nothing at all when the net turn is zero.The etag is deliberately not bumped after a write. It is part of the preview URL, so moving it reloads the element to a freshly turned preview while the CSS turn is still applied, and the picture appears to turn twice. The composable keeps its own etag for the next
If-Matchinstead, and the element keeps showing the turn until the next time the file is opened, when the regenerated preview is already upright.The fit, which is the part worth reviewing
Rotating the element alone is not enough. A landscape photo fitted to a landscape frame and then turned on its side is taller than the frame, and CSS transforms do not reflow, so it spills over the chrome above and below. The fit is now worked out against the box the picture will occupy on screen rather than its own, and zoom and pan are measured against the same box.
I reverted just that one hunk to check the test earns its place. With the fit left alone, the 3500x2335 fixture turned a quarter comes out at 947px of picture in a 633px frame:
Covered
346 unit tests, 20 of them new, over what gets offered, what gets shown, what gets written and what happens when it fails: the debounce, the net-zero skip,
If-Matchagainst the version just written rather than the stale one, the flush on navigate and on close, and the 412 path.44 e2e in Chromium and Firefox, 16 new. The write tests intercept the PUT and parse the orientation out of the uploaded bytes with a reader written inside the spec, so the assertion does not depend on the library that produced them.
Not covered: no run against a live server, so the preview regeneration described above is read off the server source rather than observed. Worth a look when testing.
👾 This pull request was assisted by Claude Code, commits carry an
Assisted-bytrailer.