Repository navigation
feat(crop): auto straighten from detected lines (Shift+S) - #1845
Open
lalibertemarc wants to merge 11 commits into
Open
lalibertemarc wants to merge 11 commits into
lalibertemarc wants to merge 11 commits into
Conversation
lalibertemarc
force-pushed
the
feat/auto-straighten
branch
3 times, most recently
from
October 5, 2026 17:18
c7189c7 to
6c94992
Compare
Adds a wand button next to the straighten ruler and a Shift+S shortcut that levels the image from its dominant near-horizontal lines. Detection runs in Rust on a 1024px proxy built like the AI mask input (raw tone curve + lens/transform warp, then orientation and flips). A structure tensor picks coherent edges, and a projection-profile search over +/-15 degrees scores how collinear they are at each angle. Returns nothing when no long line is found, so the UI shows a toast instead of guessing. The proxy uses downscale_f32_image: DynamicImage::thumbnail adds +0.5 to every channel of Rgb32F images, which washed out the earlier attempt in CyberTimon#1507. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soft horizons such as waterlines behind branches failed the line-length check at 1024px: twigs and grass took the strongest edges, and a soft edge spreads over several pixel rows. When the full-resolution search finds no confident line, search a 256px copy where fine texture blurs away, then refine within +/-1.5 degrees at full resolution. Results that already passed are unchanged. On a 28-photo test set this went from 15 to 19 detections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the pixel-vote horizon detector with the approach darktable's rotate and perspective module uses: detect line segments (LSD, via the lsdetect crate), keep near-vertical and near-horizontal ones, drop segments that disagree with the dominant vanishing point (RANSAC with self-tuning tolerance), then fit the rotation to all remaining lines. Verticals now count, so poles, posts and building edges level the photo. A direction is only trusted when its vanishing point is at least 5 image diagonals away: converging rails or roads are perspective, not roll. Results beyond 10 degrees are rejected. On 11 photos straightened by hand: within 0.5 degrees on 7 (was 1), off by more than 2 degrees on none (was 7), no answer on 2. About 10-30 ms per photo instead of ~150 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use guided_perspective's cross() and unit_norm() instead of local copies, and drop the hand-rolled RNG: vanishing point candidates now come from every pair of the 60 longest lines in each direction, so the result no longer depends on random sampling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shift+S right after switching photos hit the isReady guard and was dropped silently, so a second press was needed. Show the spinner and wait for the full image to load instead, giving up if another photo is selected in the meantime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add Auto Straighten to the Productivity submenu of the editor and library context menus. In the library it runs on every selected photo. The batch runs in Rust like Auto Adjust, sharing its sidecar and thumbnail loop (update_sidecars_from_images). Photos are decoded at full quality, two at a time, so each one gets the same angle as Shift+S and its real size for refitting the crop. The crop refit (crop_for_rotation) mirrors calculateAutoCropForRotation in cropUtils.ts. In the frontend, withRotation now serves both the manual straighten tool and auto straighten, and the post-batch reload is shared by Auto Adjust, Auto Lens Correction and Auto Straighten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Share the orientation and flip step with the render pipeline through a new apply_orientation_and_flip helper, keep the detector in f64 throughout, and name the near-zero tolerance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Running Auto Straighten from a filmstrip thumbnail sent the photo open in the editor to the background batch too. The batch command returns before it writes the sidecar, so the editor reloaded the old adjustments: only the thumbnail changed, and the next autosave put the old rotation back. Like pasting adjustments, the open photo now goes through the editor's own auto straighten (applied in place, undoable) and only the other selected photos go to the batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lalibertemarc
force-pushed
the
feat/auto-straighten
branch
from
October 7, 2026 17:01
2b6cd69 to
c762e24
Compare
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.
Description
Adds a one-click auto straighten to the crop tools: a wand button next to the straighten ruler, plus a new Shift+S keybind (plain S still toggles the manual ruler). It finds the straight lines in the photo and sets the fine rotation so that vertical structures (poles, posts, door frames, building edges) and level lines (horizons, waterlines, facades facing the camera) end up straight. The crop is refit automatically, and the result goes through the normal history, so Ctrl+Z undoes it. When it can't find trustworthy lines, it shows a toast and leaves the photo alone instead of guessing.
It's also in the Productivity submenu of the right-click menus. In the editor it straightens the open photo. In the library it runs as a batch on every selected photo, like Auto Adjust and Auto Lens Correction.
This picks up the idea from #1507, which was closed because it didn't detect lines on most images. One likely cause:
DynamicImage::thumbnailon anRgb32Fimage adds +0.5 to every channel, so that analysis ran on a washed-out picture. This PR builds its analysis image withdownscale_f32_imageinstead.The approach follows darktable's rotate and perspective module (
ashift): detect line segments, keep the ones that agree on a vanishing point, then fit the rotation to all of them at once.Type of Change
Changes Made
calculate_auto_straighten(src-tauri/src/auto_straighten.rs). It returns the absolute rotation to apply, ornullwhen nothing trustworthy is found.build_full_warped_image, i.e. raw tone curve plus lens/transform warp), then the 90° orientation and flips. The fine rotation and crop are left out. So lens correction and transforms are taken into account, and the current rotation doesn't affect the result.lsdetectcrate (see Additional Notes). Segments within 30° of vertical or horizontal are kept.crossandunit_normfromguided_perspective.rs(nowpub(crate)) instead of duplicating them.apply_orientation_and_flipinadjustment_utils.rs, used by bothapply_spatial_transformationsand the detector, so they share the same transform order.apply_auto_straighten_to_paths(library selection).update_sidecars_from_imageshelper, extracted fromapply_auto_adjustments_to_paths, which now uses it too. The loop decodes each photo, updates its sidecar, syncs XMP and regenerates the thumbnail with progress. Auto Adjust behaves exactly as before.crop_for_rotation, a Rust port ofcalculateAutoCropForRotationincropUtils.ts.Wand2button in the Rotation slider label ofCropPanel, with a spinner while it runs.handleAutoStraighteninuseEditorActions:rotationand refits the crop throughwithRotation. The manual straighten tool inEditor.tsxnow uses the same helper. This way the crop is also refit when it runs from the editor's context menu with the Crop panel closed.auto_straightenkeybind (default Shift+S, configurable like the others). It switches to the Crop panel if needed.isAutoStraighteningflag in the editor store.useAppContextMenus).handleAutoStraighten(applied in place and undoable, the same way pasting adjustments treats it). Only the other photos go to the batch. The batch command returns before it writes the sidecars, so otherwise the editor would reload stale adjustments and autosave would undo the batch result.reloadAdjustmentsForPathsreplaces the identical after-batch reload code in Auto Adjust and Auto Lens Correction. Auto Straighten uses it too.mainafter this PR was opened. The branch is rebased onto it.Screenshots/Videos
Before :
After :

Worked on almost all the photos I tested, though it is possible that the algo doesnt find a line (please enjoy my photo of the inside of a dumpster, litteraly trash photo) :
Testing
Measured with a local harness (not committed) that runs the detector on real photos (Sony ARW, Canon CR3, Nikon NEF, Fuji RAF, DNG, JPEG), and tested in the app:
npm run start's dev build, on 24–33 MP raws.main:tsc, ESLint, Prettier, the i18n checks (i18n:check,check-runtime.mjs,i18n:lint), clippy (--all-targets, 0 warnings) and rustfmt. ESLint actually reports 2 fewer warnings, because the shared reload removed twoanycasts.Test Configuration:
Checklist
Additional Notes
Why a new dependency (
lsdetect)The detection step needs real line segments: endpoints, length and direction, not just "pixels that lean a certain way".
imageproc. Sagging power lines, foliage texture and perspective lines (rails, roads) kept winning over the real horizon. That's the 1-in-11 result above.imageprocisn't enough: its Hough transform only has 1° angle resolution, and it has no segment detector.lsdetectis pure Rust and small (about 1,000 lines). Its only dependency israyon, which RapidRAW already uses. It adds one line toCargo.tomland one package toCargo.lock.License notes (please double-check)
I'm not a lawyer, so I want to flag how I read the licenses:
lsdetectis MIT OR Apache-2.0.image,imageproc,rayon,serde, …).cargo-aboutcould generate it for every dependency at once.ashift.cis GPL-3.0. No darktable code was copied: the approach (vanishing-point consensus and rotation fit) was reimplemented from scratch. GPL-3.0 and AGPL-3.0 are also explicitly compatible.lsdetectis a young crate (v0.3.0) from a single author. If it were ever abandoned, it's small enough to vendor under its MIT terms.Batch support
FULL_DECODE_WORKER_THREADS) instead of on every core. Large selections are slower than Auto Adjust, but memory stays bounded, and the thumbnail progress bar shows where it's at.calculateAutoCropForRotation) and Rust (crop_for_rotation). The batch runs entirely in Rust, like the other Productivity batch actions, so it needs its own copy.Other notes
CropPanel.tsx,useEditorActions.ts,useKeyboardShortcuts.ts,useEditorStore.ts). The overlap is purely additive, keep both sides. I've already resolved it locally with both branches merged, and I'll rebase whichever PR lands second.AI Disclaimer:
Please state the involvement of AI in this PR:
🤖 Generated with Claude Code