Skip to content

crop: apply the aspect ratio dialed in on the camera - #22088

Open
hennikul wants to merge 1 commit into
darktable-org:masterfrom
hennikul:camera-aspect-crop
Open

crop: apply the aspect ratio dialed in on the camera#22088
hennikul wants to merge 1 commit into
darktable-org:masterfrom
hennikul:camera-aspect-crop

Conversation

@hennikul

@hennikul hennikul commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Reproduces the aspect ratio dialed in on the camera as a crop that is applied
on open but stays editable.

Addresses #22087, and picks up #19109 (closed as not planned) with the
brand-generic approach @kmilos asked for there.

Rebased onto current master and squashed into one commit. Sony has been
dropped since the first push, see below.

What it does

Cameras that let you pick an aspect ratio in the viewfinder record the choice
in the raw without destroying the surrounding pixels. We showed the full
sensor readout, so the framing chosen while shooting was lost. Now the crop
module centers that ratio on the sensor area and enables itself: the intended
framing is there on open, the aspect is locked to it in the combo box, and
the whole frame is still available to reframe within or to get back by
switching crop off.

Design notes

The ratio is kept, the vendor's box is not. @kmilos pointed out in #19109
that we do not apply the same full-sensor crop as the vendor. On an EOS R6
the camera reports its 1:1 crop as 3648x3648 at x=912 against its own
5472x3648 output area, while the pipe gets 5496x3670. Rather than track each
vendor's framing, only the ratio is taken and centered on the area we
actually produce. Users get marginally more image than the camera would have
given them.

Vendor readers are a table. _check_camera_aspect() walks a list of
per-vendor functions; everything downstream is brand agnostic. Adding a
vendor is one function and one line.

Nothing happens rather than the wrong thing. The module stays off when
the ratio it works out is the one it was handed anyway, so a vendor that
applied the crop to the raw data after all, or a value read as something it
is not, does nothing rather than cropping the image wrongly.

DefaultUserCrop is untouched. The DNG path keeps its current behaviour
of only preloading the parameters. default_enabled is set exclusively on
the camera-aspect path.

Portrait. p_width/p_height are still in sensor orientation at
reload_defaults() time while crop runs after flip, so the box is swapped
when the orientation swaps axes. The box is centered, which leaves the
mirroring bits a no-op.

Vendors

Vendor Status
Canon Canon.AspectInfo (0x9a), implemented and tested
Olympus OlympusIp.AspectRatio (0x1112), implemented and tested
Nikon nothing to do, it writes only the cropped area, as @ralfbrown noted
Fujifilm not in the makernote, being done in darktable-org/rawspeed#988
Sony dropped, see below

On Sony. I had added a reader in the first push and have removed it. It
cannot be made to work through Exiv2 and I could not verify it on anything.
On the only non-native A-mount sample available (DSLR-A580 from
raw.pixls.us) the ratio lives at CameraSettings3 byte 0x0a, and Exiv2
parses that byte-oriented block as big endian shorts, so every Sony2Cs
value is two unrelated bytes glued together and 0x0a is not exposed at all.
Filed as Exiv2/exiv2#9469. Newer bodies keep the setting in the undecoded
Tag2010. Rather than ship a reader that fires on a misparsed block, Sony is
left out; it wants the rawspeed route as Fujifilm did.

Testing

EOS R6 cRAW, and Olympus samples from raw.pixls.us, exported with
darktable-cli.

Body Aspect Orientation Output
EOS R6 1:1 landscape / portrait 3670x3670 both
EOS R6 4:3 landscape / portrait 4892x3669 / 3669x4892
EOS R6 16:9 landscape / portrait 5488x3087 / 3087x5488
EOS R6 3:2 native portrait 3670x5496, untouched
E-M10 Mark IV 16:9 landscape 5200x2925
E-P2 3:2 landscape 4098x2732
TG-6 3:2 landscape 4014x2674
E-PL9 4:3 native landscape 4608x3456, untouched

Also confirmed in the GUI: crop shows enabled with the aspect locked, and
dragging reframes within the full sensor area.

The 1.85:1 entry in the Canon table is untested, no body I have offers it.

Two things worth raising

Switching a module on by default is a policy call rather than a detail, and
it is the part I would most like review on. @jenshannoschwalm noted in #19109
that the DNG path is deliberately off-but-available; I left that alone and
scoped this strictly to camera aspect settings, but I can put the new
behaviour behind a preference instead if that is preferred.

Separately, and not caused by this: modify_roi_out() aligns width and
height independently to the ratio's factors when exporting, so the pair is
only exact when both land on the same multiple. The TG-6 3:2 above comes out
at 1.5011 for that reason, and a manually set ratio behaves the same way.
Happy to file it separately if you think it is worth fixing.


Disclosure: designed, written and tested with AI assistance (Claude Code with Claude Opus 5).
Verified against current master and builds clean with no new warnings.

Comment thread src/common/exif.cc
@hennikul

Copy link
Copy Markdown
Contributor Author

On Fuji, from the earlier thread: I have opened
darktable-org/rawspeed#986 for the rawspeed half.

Reading it through Exiv2 is not possible, the ratio is in the raf container
rather than the Exif makernote. But rawspeed already parses that container -
RafDecoder::getDefaultCrop() reads tags 0x0110 and 0x0111 out of it, and
the aspect ratio is 0x0115, two along. So it is a small addition there
rather than new parsing: the tag, an ImageMetaData::cameraAspectRatio
field, and a read. If that lands, consuming it here is a handful of lines in
imageio_rawspeed.cc filling the same camera_ratio_* this PR already has,
and the crop module needs no changes at all.

That would also open the door for the other decoders, since the field is
named for the concept rather than for Fuji.

Worth flagging: I have no Fuji body, so that PR is explicitly marked
untested and asks for a verifier. I would rather say so than imply coverage
I cannot demonstrate.

For the record on the rest of the vendors, LibRaw parses this for Canon,
Fuji, Nikon, Olympus and Pentax into sizes.raw_aspect, and gets a
rectangle for Sony and Panasonic into sizes.raw_inset_crops. So the
per-vendor knowledge exists and is not in doubt; it is only a question of
which layer reads it. darktable routes only cr3 and x3f through LibRaw,
which is why rawspeed looked like the right home rather than a second
metadata pass over every raw. Happy to go the other way if you would prefer.

Disclosure: written with AI assistance (Claude Code with Claude Opus 5).

@gi-man

gi-man commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

There should be enough images in Play Raw on pixl.us to test more vendors. I think there is also the images from raw.pilx.us

@hennikul

Copy link
Copy Markdown
Contributor Author

Rebased onto current master and squashed to one commit, so the earlier
commits this thread refers to are gone. Two things changed with it.

Sony is out. @TurboGit, I added it when you asked and I have now removed
it, because I could not make it work and could not verify it. On the only
non-native A-mount sample I could find (DSLR-A580 on raw.pixls.us) the ratio
lives at CameraSettings3 byte 0x0a, and Exiv2 parses that byte-oriented
block as big endian shorts: DriveMode 4121 is literally bytes 8 and 9 of
the array glued together, and byte 0x0a is never exposed. Newer bodies keep
the setting in the undecoded Tag2010. I have filed the Exiv2 side as
Exiv2/exiv2#9469. Shipping a reader that fires on a misparsed block seemed
worse than shipping no reader, so Sony is left out with a note on where the
data lives.

Olympus is no longer untested. raw.pixls.us had suitable samples:
E-M10 Mark IV at 16:9, E-P2 and TG-6 at 3:2, and E-PL9 at the native 4:3 as
a control. All four behave, table in the description.

Fujifilm is darktable-org/rawspeed#988, verified there against an X-T200
shot at 16:9 with an X100V 3:2 control. Consuming it here is a handful of
lines once it lands and needs no changes to the crop module.

So of the four you asked about: Olympus and Canon work and are tested,
Fujifilm is in flight in rawspeed, and Nikon and Sony have nothing to offer
here for the reasons above.

Still open from my side is the question in the description about switching
the module on by default, which is the part I would most value a steer on.

Disclosure: written with AI assistance (Claude Code with Claude Opus 5).

@wpferguson

Copy link
Copy Markdown
Member

As far as supporting cameras, I think we support the ones that have exiv2 tags and when the rest are supported by exiv2 then we add/enable them.

I don't think we want to get into writing and supporting our own separate raw file metadata decoding.

If someone wants to provide patches upstream to exiv2 that would be good.

@hennikul

hennikul commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Agreed, and that is what this does - Canon and Olympus are read from Exiv2
tags only, nothing hand-rolled here.

On upstreaming: that is exactly where Sony went. Exiv2 misparses the camera
settings block for the A33/A55/A450/A580/NEX family - it is an array of bytes
read as shorts, so every Sony2Cs value is two bytes glued together and the
aspect ratio at byte 0x0a is unreachable. On a DSLR-A580 Sony2Cs.DriveMode
reports 4121, which is bytes 8 and 9 read as a big endian short. Filed as
Exiv2/exiv2#9469 with a fix in Exiv2/exiv2#9470, verified against the nine
affected models with samples on raw.pixls.us and with the other eight Sony
bodies I could find byte for byte unchanged. If it lands, Sony here becomes a
ten line reader in the same table, which is the workflow you describe.

Fuji is the one case I would like your read on. The value is in the raf
container rather than the Exif makernote, so Exiv2 cannot see it. I put it in
rawspeed (darktable-org/rawspeed#988) on the grounds that it is not new
parsing: RafDecoder::getDefaultCrop() already reads 0x0110 and 0x0111 out of
that same directory, and the aspect ratio is 0x0115, two along, so it is three
small reads next to code that is already there. But if you would rather that
knowledge lived in Exiv2 too, say so and I will take it there instead; it is a
bigger change there, since Exiv2 does not parse that container at all today.

Disclosure: written with AI assistance (Claude Code with Claude Opus 5).

@wpferguson

Copy link
Copy Markdown
Member

Does exiftool support Fuji aspect ratio?

Maybe we support all the "odd" cameras with exiftool and a lua-script than runs on import and populates the field

@TurboGit TurboGit added priority: low core features work as expected, only secondary/optional features don't scope: image processing correcting pixels scope: DAM managing files, collections, archiving, metadata, etc. release notes: pending labels Sep 1, 2026
@TurboGit TurboGit added this to the 5.8 milestone Sep 1, 2026
@hennikul

hennikul commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Yes - RawImageAspectRatio, tag 0x0115 in ExifTool's FujiFilm::RAF table. I
have read 16:9 off an X-T200 with it.

The precedent is there too: eleven of the bundled lua-scripts already call
exiftool, and contrib/autostyle.lua is exactly "exif condition -> apply
style". So this would work for the long tail.

Two limits worth knowing before betting on it. The lua image API has
apply_style and apply_sidecar but no way to set module defaults, so the
crop would arrive as applied history rather than as a default - discarding
history wipes it, and it is an edit rather than a starting point. And because
crop params are fractions of the frame, a style encodes a box relative to
sensor shape: a 16:9 style made on a 3:2 body is wrong on a 4:3 sensor, so it
would need styles per ratio and per sensor shape, not just per ratio. Add an
exiftool subprocess per image at import and it gets expensive on a large
library.

So I would see it as a good optional add-on for cameras we cannot reach,
rather than a replacement for the in-tree path where Exiv2 already has the
tag - for Canon and Olympus that path is exact and costs nothing at import.

Disclosure: written with AI assistance (Claude Code with Claude Opus 5).

@wpferguson

Copy link
Copy Markdown
Member

The lua image API has
apply_style and apply_sidecar but no way to set module defaults, so the
crop would arrive as applied history

We could add the exif tags to metadata editor, which then makes them accessible to Lua, so Lua could populate the image field after getting the info from exiftool. Once the value is set, it's stored in the database and accessible to other modules.

@hennikul

hennikul commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

That works, and the machinery is already there - src/libs/metadata.c has the
UI for adding a metadata entry via dt_metadata_add_metadata(), and lua
already reads and writes those through dt_metadata_set/dt_metadata_get in
src/lua/image.c. So no new plumbing needed.

It also solves the objection I raised: because it lands in the database, the
crop module could read it in reload_defaults() as a default rather than
the crop arriving as applied history. That makes the two approaches
complementary - the vendor table here for what Exiv2 can read, and a
lua/exiftool path populating the metadata field for the long tail, with
reload_defaults() falling back to it when the readers find nothing. Happy to
add that fallback to this PR if you want it, or leave it for a follow-up once
there is a script to feed it.

One thing worth deciding deliberately: it puts a camera fact into the
user-editable metadata namespace, so it shows in the metadata editor and goes
out to XMP, and clearing or editing it would silently change the crop default.
Not a blocker, just a consequence worth choosing on purpose.

Disclosure: written with AI assistance (Claude Code with Claude Opus 5).

@gi-man

gi-man commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@hennikul Can you add to your Instructions for Claude section to be very concise if you are just going to copy/paste claude text? I'm not a maintainer on this repo and reading all of this takes time from the maintainer and all the other PRs for him to review/build/test.

@TurboGit

TurboGit commented Sep 3, 2026

Copy link
Copy Markdown
Member

@hennikul : We need a release notes entry for this. TIA.

Cameras that let you pick an aspect ratio in the viewfinder record that
choice in the raw file without destroying the surrounding pixels. Other
editors reproduce the framing on open and let you take it back; we showed
the full sensor readout and dropped the intent on the floor.

Read the ratio into the image and let the crop module center it on the
sensor area, enabling itself so the framing is there right away while
staying free to be moved around within the full frame.

We deliberately keep the ratio alone instead of the box the camera reports.
That box is centered on the output area of the camera, which is not quite
the area we hand to the pipe, so reusing it would mean matching every
vendor's idea of the full frame. Centering a ratio ourselves sidesteps
that, and it is also the only thing some vendors record.

The module stays off when the ratio it works out is the one it was handed
anyway, so a vendor that applied the crop to the raw data after all, or a
value read as something it is not, does nothing rather than cropping the
image wrongly.

Canon and Olympus are read for now, but the reader is a table of per-vendor
functions and everything downstream is brand agnostic, so adding a vendor
is one function and one line. Nikon needs nothing, it writes only the
cropped area. Fuji and Sony are not reachable through Exiv2 and want doing
in rawspeed instead.

A DefaultUserCrop coming from the raw keeps its current behaviour of only
preloading the parameters, untouched.

Tested on an EOS R6 across 1:1, 4:3 and 16:9 in landscape and portrait with
native 3:2 left alone, and on Olympus E-M10 Mark IV (16:9), E-P2 (3:2),
TG-6 (3:2) and E-PL9 (native 4:3) samples from raw.pixls.us.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: low core features work as expected, only secondary/optional features don't release notes: pending scope: DAM managing files, collections, archiving, metadata, etc. scope: image processing correcting pixels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants