Skip to content

Spektrafilm (dev branch) analog film simulation module - #21534

Draft
piratenpanda wants to merge 72 commits into
darktable-org:masterfrom
piratenpanda:spektrafilm
Draft

Spektrafilm (dev branch) analog film simulation module#21534
piratenpanda wants to merge 72 commits into
darktable-org:masterfrom
piratenpanda:spektrafilm

Conversation

@piratenpanda

@piratenpanda piratenpanda commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

This module brings the spektrafilm project (https://github.com/andreavolpato/spektrafilm) to darktable. Spektrafilm simulates analog film processes and applies them to digital images.

This module has been derived from the spektrafilm project and tries to stay as close or exact to the algorithms used there.

Currently it looks like this:
grafik

Film and print stocks can easily be selected via drop downs. Further settings are then handled inside the respective tabs (currently untranslated).

Default settings are Portra 400 and Portra Endura which transform your image from
grafik

to
grafik

Currently almost every film/paper combination from the spektrafilm dev branch is working except for Kodak 2302. Grain, Halation and Diffusion models are working as well.

Discussion thread and the python script needed to create the necessary spektrafilm data for darktable can be found here: https://discuss.pixls.us/t/spektrafilm-darktable-module-sort-of/58744/

Current dtdocs PR darktable-org/dtdocs#993

@piratenpanda
piratenpanda marked this pull request as draft July 12, 2026 07:57
Comment thread src/common/spektra_core.c
Arecsu added a commit to Arecsu/spektrafilm that referenced this pull request Jul 14, 2026
Uses spektrafilm's dev branch (git+https://github.com/andreavolpato/spektrafilm@dev)
to generate pack.json, spectra_lut.f32, and profiles/*.json for the native
darktable module (PR darktable-org/darktable#21534).

Run: uv run --python 3.13 --with 'spektrafilm@git+https://github.com/andreavolpato/spektrafilm@dev' python3 export_pack.py -o ~/.config/darktable/spektrafilm
@Arecsu

Arecsu commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

This is just amazing, love this

Comment thread src/iop/spektrafilm.c Outdated
Comment thread src/iop/spektrafilm.c Outdated
@Arecsu

Arecsu commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Right now there is a problem where, if I load up an already existant photo + XMP settings from my database, the module won't show up and I will get this in the CLI:

   312.1009 cannot get iop-order for spektrafilm instance 0
   467.4585 cannot get iop-order for spektrafilm instance 0
   467.4784 cannot get iop-order for spektrafilm instance 0

I have to edit a new image from a clean slate in order for the spektrafilm module to show up.

Other than that, working awesome. This is the scariest, closest film simulation look and feel I've ever got from any photo editing pipeline, in any software. And oh god did I spent a crazy amount of time on it. Main reason I fell in love with Darktable was because I could get really close to it with the existent modules. AGX, Diffuse and Sharpen, LUTs... Until I came across spektrafilm, and now this module... it is a game changer. There's currently no way to achieve these visuals otherwise. Props to @andreavolpato!

@piratenpanda

Copy link
Copy Markdown
Contributor Author

@Arecsu should be fixed with the latest commit

@Arecsu

Arecsu commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@piratenpanda Thank you!

You may be interested in Arecsu@f2ce151, which I made on top of your PR

I've created this pre-compression boost as I find myself reaching out the tone curve module very often to push the histogram to the right when I use spektrafilm, because it tends to aggressively limit the highlights ceiling in some cases. This slider should preserve the characteristics of the module, gently pushing or pulling the histogram.

Feel free to test it and incorporate it into your PR in any case!

@Arecsu

Arecsu commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Hey, I found another reference: this Davinci Resolve OFX based on spektrafilm and exposing far more options, in a nice workflow as well:

I think the UI/UX and the options exposed by it are more appealing here in darktable for this module, as well as the logic behind them.

Here at 5:45 you can see it being used. It does seem comprehensible and with many interesting sliders and selectors as well.

@piratenpanda

Copy link
Copy Markdown
Contributor Author

Thanks for the input. I'd like to move design/implementation discussions to the pixls thread to make it easier to follow :)

@TurboGit

Copy link
Copy Markdown
Member

@piratenpanda : I would edit the first message to give a high level description of what this does, what is the goal and maybe some screenshots with before/after to illustrate the points.

@piratenpanda

Copy link
Copy Markdown
Contributor Author

@TurboGit initial update done. Thanks!

@piratenpanda piratenpanda changed the title Initial darktable spektrafilm (dev branch) module adds a spektrafilm (dev branch) analog film simulation module Jul 15, 2026
@piratenpanda piratenpanda changed the title adds a spektrafilm (dev branch) analog film simulation module Spektrafilm (dev branch) analog film simulation module Jul 15, 2026
Arecsu added a commit to Arecsu/spektrafilm that referenced this pull request Jul 18, 2026
The C port of spektrafilm (PR darktable-org/darktable#21534) needs a
data pack — pack.json, spectra_lut.f32, and profiles/*.json — at
~/.config/darktable/spektrafilm/. This script extracts the data from
the Python package using colour-science and numpy.
Arecsu added a commit to Arecsu/spektrafilm that referenced this pull request Jul 18, 2026
Uses spektrafilm's dev branch (git+https://github.com/andreavolpato/spektrafilm@dev)
to generate pack.json, spectra_lut.f32, and profiles/*.json for the native
darktable module (PR darktable-org/darktable#21534).

Run: uv run --python 3.13 --with 'spektrafilm@git+https://github.com/andreavolpato/spektrafilm@dev' python3 export_pack.py -o ~/.config/darktable/spektrafilm
@TurboGit TurboGit added priority: low core features work as expected, only secondary/optional features don't feature: new new features to add documentation: pending a documentation work is required release notes: pending labels Jul 24, 2026
@TurboGit TurboGit added this to the 5.8 milestone Jul 24, 2026

@TurboGit TurboGit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Another pass... some more questions.

Comment thread src/iop/spektrafilm.c
Comment thread src/iop/spektrafilm.c Outdated
Comment thread src/iop/spektrafilm.c Outdated
Comment thread src/iop/spektrafilm.c Outdated
Comment thread src/iop/spektrafilm.c Outdated
Comment thread src/iop/spektrafilm.c

typedef struct dt_iop_spektrafilm_params_t
{
uint32_t film_hash; // $DEFAULT: 0 (0 = first available filming stock)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Those hashes are references to the on disk .json / lut, right?

So has for LUT3D module, the module's parameters is not self contained?

My question : Would it be possible to have all stored into module's params? What is the size of the data needed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here my concern is that for a scene to display final step it is not good to have to rely on external data.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Per-image data: one film + one paper profile, packed as raw float32 with the JSON overhead stripped: ~32 kB. The bulk is density_curves_layers (256×3×3), density_curves (256×3), log_sensitivity, channel_density, base_density, midscale_neutral_density (81×3 each). And that's the current state. Will only get bigger as spektrafilm evolves.

Shared data: identical for every image, so it can't live in params at all sensibly: the hanatos2025 spectral upsampling LUT, CMFs, spectral locus, 8 illuminant SPDs, 4 brands × dichroic filter curves, the neutral print filter database, and per-stock coupler/grain/halation tuning. That's the whole pack.json plus spectra_lut.f32.

So even the per-image half is ~32 kB of params per history item, and it still wouldn't be self-contained without the shared half.

We could add the data to /data like noiseprofiles? And add the python script to create the data to /tools maybe?

@TurboGit TurboGit Jul 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could add the data to /data like noiseprofiles? And add the python script to create the data to /tools maybe?

If we can do that it would be perfect. An XMP will be self contained then as all XMP (except LUT3D) currently.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think having them as external data (not embedded to the module code) is good for two reasons:

  1. If someone wants to reference the data in isolation to the code, maybe they want to create their own print and film stocks and need a reference to point out without having to point out to the source code of darktable
  2. Following the previous point, for them to be able to load and the community to share / experiment with new print and film stocks in an easier way, having a directory where they can drop them and have darktable to load them, similarly to the LUT module in some way maybe?

There could be default data that comes with Darktable installation, like the current film stock and print papers from the module, and then data added by the user somehow in some directory

Hopefully I'm not mistaken this discussion and what I'm saying is related to the topic 😅

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Profiles aren't content-hashed yet, if a stock's measured curves are revised the name is unchanged and the render silently differs

Ok, so from version to version we are going to break edits :( Not good... I would say that this should be fixed in spektrafilm, a hash should always refer to the same "object".

So we have the option to ship it as darktable data alongside noiseprofiles and the kernels, this means adding: spectral LUT 5.7 MiB, 31 stock profiles 2.8 MB, plus 70 kB of constants to the tree. That's not my decision to make.

It's a lot indeed... Alternatively the module could download the data at first use if not yet downloaded. Probably the way to go if you ask me at this stage now that I have a better understanding about the data. That's to be done once for all dt instance (download into $HOME/.local/share/darktable/spektrafilm). This is a bit like what is down for AI modules but automatically in the spektrafilm case.

But I fell that we have many things to clarify to ensure a proper Darktable integration. So let's continue the discussion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, so from version to version we are going to break edits :( Not good... I would say that this should be fixed in spektrafilm, a hash should always refer to the same "object".

Yes, maybe I didn't properly word it. This would need keeping spektrafilm files constantly available or at least downloadable so we can do something like:

XMP records: lut_hash = 565f4ec4, profiles_hash = a1b2c3d4
darktable ships: spektrafilm/0.3.3/ spektrafilm/0.4.0/
on load: resolve hashes → 0.3.3 set → identical render

The mechanism should make sure it doesn't spam the user's folders but also keep data readily available

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@andriiryzhkov does the AI component offer a reusable fetch mechanism? Like if spektrafilm version x.y. is not in the user's folder download the data from git repo xyz?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

spectral LUT 5.7 MiB

Drive-by comment, fpzip could cut it down to 1.17 MiB but not sure if that is a) enough and b) worth the additional dependency.

@piratenpanda piratenpanda Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Latest code does the following:

  • Prefer pack data in config folder
  • If none is found and the image has not been edited yet, the module does show a download data button only, downloads the latest pack data currently from https://github.com/piratenpanda/darktable-spektrafilm and let's you start editing. This also downloads the correct hash for older edits when no data is found and such an image is opened first.
  • If a hash of a previous/old version is found in the xmp data, it shows a version mismatch and offers to download the appropriate hash from the git repo

This ensures color, film and paper data match the edit and stay consistent.

What is not yet implemented or needs to be discussed further is what happens when spektrafilm's algorithms for something else change. E.g. Grain, halation, new couplers for new paper/film data, etc. A drop down like filmic's color science versions could fill that gap but would of course add even more complexity to the module.

Current min pack version is 2, before release we can of course set this to one if the format and mechanism has been finalized.

Comment thread src/iop/spektrafilm.c Outdated
Comment thread src/iop/spektrafilm.c Outdated
@anoderay

anoderay commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Regarding the discussion about including the spektrafilm data pack: IMHO, we don't currently have any other module that depends on an active internet connection to work out of the box. Especially for opening older edits offline. (Yes, the AI features require a download, but they don't affect existing edits.)
A possible compromise: bundle the latest data pack by default, and fall back to the newly implemented download functionality only when an older edit references a data pack version that isn't bundled.

Given how actively spektrafilm is being developed, I also think it would make sense to include a version switcher for the filmic-style presets as already discussed.

@da-phil

da-phil commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Nice module, thank you for taking on the task to bring spektrafilm to darktable!
After having had another look at the original python implementation, I wonder if we also want to establish a couple of (unit)tests (in src/tests) to ensure functionality doesn't silently break when changes are made to the code.

@piratenpanda

Copy link
Copy Markdown
Contributor Author

Nice module, thank you for taking on the task to bring spektrafilm to darktable! After having had another look at the original python implementation, I wonder if we also want to establish a couple of (unit)tests (in src/tests) to ensure functionality doesn't silently break when changes are made to the code.

I ported the unit tests. Currently no pack data is in this PR but as darktable will ship the most recent one the tests are already in place currently looking at /data/spektrafilm. Will update once we know which version will be shipped with darktable or at all.

piratenpanda and others added 27 commits August 8, 2026 10:06
…d missing glare stage and scanner feature set
…n film and print for the respective part, reset to default value
… proper bauhaus widget, add blank lines between routined, use GList where necessary, use dt_hash(), add underscore prefex to static routines
The new module was missing from the `all` modules group listing.
The reference's multiplicative USM (_finalize_grain in grain.py) runs on the
absolute density -- the grain sampler's floor is still present and only
removed afterwards (add_micro_structure -> blur -> USM -> -= density_min).
The delta-model combine already took the floor back out, so pass it back in
as an offset -- added to both operands before the D/blur(D) ratio and
subtracted from the result, on both CPU and GPU: without the floor the ratio
is ill-conditioned in the deepest shadows and the USM amplifies shadow noise
the reference never does.
Comment thread spektrafilm-unit-tests.patch Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation: pending a documentation work is required feature: new new features to add priority: low core features work as expected, only secondary/optional features don't release notes: pending

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants