WIP: Spektrafilm packv3 - #22335
Draft
piratenpanda wants to merge 9 commits into
Draft
WIP: Spektrafilm packv3#22335piratenpanda wants to merge 9 commits into
piratenpanda wants to merge 9 commits into
Conversation
piratenpanda
marked this pull request as draft
September 19, 2026 10:43
piratenpanda
force-pushed
the
spektrafilm_packv3
branch
4 times, most recently
from
September 21, 2026 05:00
2109c20 to
30cf119
Compare
The engine's print curve morph already carries per-channel gamma (morph_gamma_r/_g/_b, the reference's gamma_rgb beside its scalar gamma), but nothing set it: commit_params only wrote morph_gamma, so the three stayed at their 1.0 defaults and the paper's layers could only be moved together. Expose them as print_gamma_r/_g/_b, multiplicative on print_contrast the way the engine combines them, and put the sliders in the print chemistry section next to development time. The fields are appended, so legacy_params gains an explicit v2 layout to copy through. That also fixes the v1 path, which memcpy'd sizeof(current) out of an older blob.
sf_fetch_resolve_pack_dir() answers "which pack do I render this edit with"; nothing answers "which packs are on disk", which is what a pack-management UI needs before it can offer a choice. sf_fetch_list_packs() walks the same two locations the resolver does and returns them in the same precedence order, reading the same 32-byte LUT header rather than loading anything. The header peek grows an optional id-string output on the way, so an entry can name its table and not just hash it. Excludes what the resolver excludes: a directory whose header hash disagrees with its name, one missing pack.json or profiles/, and the ".incoming-<hash>" directory of a download in progress.
Upstream's GrainParams.blur is 0.89 on the experimental branch, fitted by study b80 jointly with the multiplicative density unsharp mask. Both halves of that mask are already here at their b80 values (grain_usm_sigma 0.7, grain_usm_amount 1.5), so grain was being softened by less than the mask was built to take back. Not gated on the pack's spektrafilm_version, which cannot express the difference: upstream's main and experimental branches both report 0.3.4 in pyproject.toml while carrying 0.65 and 0.89 respectively, so a pack exported from either declares the same string. This is the failure the lut_hash exists to avoid, and grain blur is not a property of the spectral table either. Gated instead on the edit, which is what the value actually belongs to: SF_GRAIN_BLUR_FACTOR becomes grain_blur_base, appended to the v3 tail, and legacy_params() pins migrated edits to 0.8 so no existing render moves. The shipped presets keep 0.8 for the same reason. Both pipelines compute the sigma host-side, so the kernel is unchanged.
* consume reflectance spectral upsampling tables
A pack has always held exactly one spectral upsampling table, implied by
spectra_lut.f32 and consumed one way: integrate the stored spectra
against the film sensitivity. pack_format 3 names its tables in
pack.json's spectral_upsampling array and may carry a reflectance one
beside the irradiance one, which is consumed differently.
Loading. The loader fills a table array either way -- a format 2 pack's
single file is entered as the irradiance table it always was, so nothing
past the loader needs a per-format branch. The declaration decides which
files to read and how to consume them, never identity: each table's hash
and id still come from its own header, because a declaration can name the
wrong file and the hash is what every edit records. A reflectance table
with no scene illuminant is refused rather than consumed under the film's,
and so is a file name with a path separator in it, which would reach
outside the directory the fetcher verified.
Consuming. A reflectance table stores a surface, not light, recovered
under its own scene illuminant. Relative to the irradiance path it needs
the stored spectra relit by the film's reference illuminant; the result
normalized per channel on the table's own emitted neutral, sampled
through the same cubic the runtime addresses the table with, so a method
with sharp near-white structure still resolves to gray; the input CAT16
and the gamut-compression remap both centered on the table's scene white
rather than the film's, since that is the white the runtime projects
under and the relight is what carries the film's illuminant instead; and
both halves of the hanatos sensitivity adaptation skipped, having no part
in the reflectance formula.
Getting any of those wrong renders plausibly rather than failing, which
is why the pack refuses to describe a reflectance table incompletely.
Selection is by content hash, not identifier: a pack can revise a
method's table without renaming it, and an edit has to pin the one it was
developed against. 0 takes the pack's default, which is what every
existing edit and every format 2 pack gets.
Validated against the reference: building the arctic2026beta04 table with
kodak_portra_400 sensitivities matches upstream's own interpolator and
formula to 1.8e-14 over all 110592 cells, and a neutral input lands on
(1,1,1) exactly.
SF_PACK_FORMAT_MAX stays 2 here. Nothing can load a format 3 pack until
the module can also choose between its tables.
* choose the spectral upsampling table, and load format 3 packs
With the engine able to consume a reflectance table, the module needs a
way to pick one and SF_PACK_FORMAT_MAX has to admit the packs that carry
them. Both land here, together: widening the range earlier would load a
format 3 pack with no way to reach anything but its default table.
upsampling_hash records the choice by content hash, like film_hash and
paper_hash beside it, so a pack revision that reorders or drops a table
leaves the edit still naming the one it was developed against. 0 is the
pack's default, which is what every existing edit gets and the only thing
a format 2 pack offers -- legacy_params() sets it explicitly for the same
reason grain_blur_base is set there.
The combobox is built from the loaded pack rather than from params
introspection, since its entries are whatever that pack carries. It is
hidden below two entries: a format 2 pack offers no choice, and a
one-entry combobox is a control that cannot be used. The tables are
snapshotted alongside the profiles in _rescan(), because both come from
the same pack and a reload that changes one changes the other.
Both halves of the hanatos sensitivity adaptation are shown inert while a
reflectance table is selected. They belong to the irradiance formula and
the engine does not run them, so leaving them live would offer two
switches that do nothing.
The selection also joins the sim cache key: it decides which table the
whole tc LUT is built from, so it is a different sim and not a different
parameter within one.
* define sf_table_t before the struct that holds an array of it
struct sf_pack_t declares sf_table_t tables[SF_MAX_TABLES], but the
typedef sat below it, so the file did not compile:
spektra_sim.c:315:3: error: unknown type name 'sf_table_t'
Move the typedef above the struct. No other change.
* downloads land under the data directory, not the cache
_packs_dir() builds <user data>/darktable/spektrafilm/packs, and says
why in its own comment: a pack cannot be rebuilt from anything on the
machine, so clearing the cache would leave every edit made against an
older spectral table unreproducible until it was fetched again. The file
header described the cache directory instead, which is where someone
looking for a downloaded pack would not find one.
* read the table list from the pack directory, not the loaded pack
The spectral upsampling combobox never appeared. _scan_tables() asked
the loaded pack what tables it had, but _pack is assigned only by
_ensure_sim(), which runs in the pixelpipe -- so it is still NULL while
gui_update() builds the GUI. The list came back empty, and the widget
hid itself on the "nothing to choose" rule and stayed hidden.
_scan_profiles() does not have this problem because it reads the pack
directory rather than the loaded pack. Do the same here.
sf_pack_peek_tables() reads pack.json and each table's 32-byte header and
nothing else, so the GUI can ask what a pack declares without loading
tens of megabytes it has no use for. It reports the tables default-first,
the order sf_pack_load() puts them in, so a position means the same thing
to both. A directory that is not a readable pack reports no tables, which
stays the existing "no choice" case rather than becoming an error.
* resolve the automatic paper the same way in the GUI and the pipeline On a stock that names no target print, the print development slider was live but inert, and reset to a time that did nothing. _auto_paper_entry() picks the automatic paper in three tiers: the film's named target print, then a print stock with the same channel model, then the first printing entry there is. _resolve_stock() had only the first and the last. The middle tier is the one that matters, because target_print is optional in the pack and the entry list is sorted by display name, so the last resort bears no relation to the film. Kodak Double-X names no target print, so the two disagreed: the GUI resolved it to Kodak Print Film 2302 and enabled the slider over that paper's five development times, while the pipeline printed on Fujifilm Crystal Archive Type II, which is characterised at one development and ignores the setting. A B&W negative was being auto-printed on color paper, and the slider described a paper that was not in use. Give _resolve_stock() the same channel-model tier, and pass the film's model from commit_params(). The film lookup passes -1, having no such preference. Both now resolve Double-X to Kodak Print Film 2302. * put the print development slider on the paper in force Selecting Double-X left the print development time reading 0 even though the paper it resolves to, print film 2302, is characterised at five times. 0 renders correctly -- it means the stock's own default, and the loader takes the middle member -- but it is the one discontinuity in the slider's range, so it reads as though nothing is set. _film_changed() already re-baselines the film's own time on a stock switch, for the reason the comment there gives: a time from the previous stock means nothing on this one. The print side needs the same, because a film switch can move the automatic paper -- Double-X resolves to 2302, a color negative to a color paper -- and it was never updated. The auto branch of _paper_changed() made it worse by zeroing the time rather than resolving what auto points at. Both now take the time from the paper actually in force. Only on auto: an explicitly chosen paper has not changed, so neither should its time. Resolving "the paper in force" was already spelled out inside _update_development_sensitivity(); it is now _effective_paper_entry(), shared by all three callers so they cannot drift apart. Double-X lands on 5 min. A color negative stays at 0, its auto paper having no development family, which is what 0 is for.
* log which pack the table list came from
The spectral upsampling combobox hides itself below two tables, so a
pack that declares fewer than expected is indistinguishable from a build
that has no such control. The usual cause is a pack installed somewhere
the module does not look, which the existing pack-resolution line does
not show either: it reports the directory, not what was found in it.
Report the count, the directory, and each table's hash and id, beside
the resolution line that names the directory.
* find a pack by any table it carries, not just its default
An edit records the spectral table it was developed against. The
manifest match only ever compared the pack's top-level lut_hash, which
names its DEFAULT table, so an edit made with any other table of a
pack_format 3 pack could not fetch the pack that holds it: the download
reported that no pack with that table is published, while the pack
sitting in the manifest carried it all along.
Also consider the per-table rows the manifest lists under "tables". The
directory a pack installs into is still named for its default table's
hash, which is what the resolver expects to find and what the entry
already reports.
A format 2 pack lists no tables and matches as before.
Against a manifest holding the 0.3.4 pack, an edit asking for the arctic
table now resolves to it instead of failing; an edit asking for the
hanatos table resolves as it already did, that being the default.
* resolve an edit to the pack it was made with
lut_hash names the spectral table and nothing else. A release can carry
a table forward byte-identical while its profiles move -- hanatos2025 is
the same bytes in 0.3.3 and 0.3.4, while all 31 profiles differ by up to
0.42 density on the print films. Two packs then answered to one
lut_hash, and an edit had no way to say which it was developed against:
opening it against the other one rendered differently and reported
nothing, the recorded table having genuinely matched.
Packs now declare a pack_hash over their constants, profiles and tables.
Read it into sf_pack_t, record it in the edit beside lut_hash under the
same rule -- stamp while they still agree, never overwrite a
disagreement, since that would erase the mismatch the field exists to
report -- and prefer it everywhere a pack is chosen:
- resolving the directory, before the table is consulted at all, since
two installed packs can carry one table
- matching the manifest, where an edit naming a pack gets that pack or
nothing rather than falling back to the other one carrying its table
0 means an edit made before packs had an identity, or a pack that
declares none. Both fall back to matching the table exactly as before,
so nothing existing changes behaviour. Presets record 0 deliberately:
a preset is not tied to the pack it was authored on.
* always show the spectral upsampling selector Hiding the selector below two tables made a pack carrying fewer tables than expected indistinguishable from a build without the feature. Show it always, insensitive when there is nothing to pick, so it names the table in use either way. * pin a table-less legacy edit to the first published pack An edit migrated from params version 1 or 2 that recorded no spectral table was made when exactly one data pack existed -- 0.3.3, carrying hanatos2025 at 565f4ec4. There was nothing else to render with, so that is the table it used. Left at 0 it resolved instead to whatever is installed, which on a machine that has since moved to a later pack means different profiles and a different render, reported as "no table recorded, taking what is installed". Set in legacy_params() and nowhere else, because that is the only place the record's own version proves it predates the field. A fresh edit also starts at 0 and must keep resolving to the default pack, so the resolver cannot tell the two apart and must not try. * name the print gamma controls as the reference does The reference calls these gamma_factor and gamma_factor_red/green/blue. darktable's film side already follows that -- film_gamma_factor is labelled "development gamma" -- so only the print side called the same quantity contrast, and the three per-channel sliders inherited the odd name out. Label the scalar "print gamma" and the channels "print gamma red/green/blue", and follow the wording through their tooltips. The print_contrast field keeps its name: it is what presets and styles refer to, and renaming it would buy nothing the label does not. Placement is unchanged -- the scalar stays outside the chemistry collapsible, the three channel trims inside it. * offer every installed table, not only the resolved pack's An edit made against 0.3.3 resolves to a pack carrying one table, so the selector listed one entry and sat inert. The edits most likely to want a method a later pack added were exactly the ones that could never reach it. List the tables of every installed pack instead, one entry per table -- packs overlap, 0.3.3's table being byte-identical to the one 0.3.4 carries, and two entries for it would read as two methods while picking between the packs arbitrarily. First pack wins, which is the precedence sf_fetch_list_packs() already reports. Choosing a table from another pack has to move the edit to that pack, or the pipeline would resolve to the old one and fail to find the table. The callback releases the recorded pack and table so resolution follows the choice, and both are stamped again on the next render, naming whichever pack actually supplied it. Resolution consults an explicitly chosen table first for the same reason: it may only exist elsewhere, which is what choosing it means. * show the method name for a pack that declares none pack_format 2 named no tables, so its header says only the kind -- "irradiance_xy_tc@0.3.3" -- and that string reached the combobox, where everything else reads as a method name. Two ways to the right one. A later pack carrying the same table declares it, so its identifier teaches the entry its method even though the bytes come from the earlier pack; the directory stays the earlier pack's, precedence being about which files to read. Failing that, the first published table is named outright: exactly one pack ever declared no tables, and this is the one it held. Reads hanatos2025 either way, with or without a later pack installed. * record what params version an edit was born at Installing 0.3.4 changed how existing edits rendered. An edit predating pack_hash carries only its table, 565f4ec4, and 0.3.4 carries that table forward byte-identical -- so it answered the edit exactly as 0.3.3 does, outranked it as the hand-installed pack, and rendered it on profiles that differ by up to 0.42 density. pack_hash cannot express the fix, because the pack the edit wants declares none. origin_version records the params version the edit was first written at, which does: 1 and 2 predate pack identity, and so predate every pack that carries one, so such an edit belongs on a pack that declares none. legacy_params() sets it; the resolver looks for an installed pack carrying the table that also declares no identity. A field rather than a reserved pack_hash value. pack_hash 0 is where every fresh edit sits until its first render stamps it -- stamping needs a loaded pack, which needs a resolution -- so 0 cannot also mean old, and a sentinel inside the hash space would collide with a real pack eventually. It also generalises: a later migration can ask how old a record is without inventing another marker. pack_hash is never stamped onto such an edit. Reaching an identified pack means its own is not installed, and stamping would make the substitution permanent, surviving the moment its pack comes back. That case is now logged, being the one substitution that changes the picture while every hash still matches. With 0.3.4 hand-installed above 0.3.3: a migrated edit resolves to 0.3.3, a fresh one to 0.3.4, an edit naming 0.3.4 to 0.3.4, and choosing a table still moves the edit to whichever pack supplies it. * gray the adaptation pair as soon as a reflectance table is picked Both halves of the hanatos sensitivity adaptation belong to the irradiance formula, and sf_sim_build() does not run them for a reflectance table. They were already shown inert for one -- but only from gui_update(), which does not re-run when the combobox changes. So selecting arctic2026beta04 left the two toggles live until the module was reloaded, offering switches that changed nothing. Move the decision into a helper and call it from the selection callback as well, that being exactly the moment the pair becomes inert or live again. * offer the pack a legacy edit needs instead of substituting one An edit predating pack identity, opened on a machine where only a later pack is installed, rendered on that pack. The table matched, so nothing objected -- but a release can carry a table forward byte-identical while its profiles move, which is what 0.3.3 and 0.3.4 do, so it rendered differently with every hash in agreement. All 31 profiles differ there, by up to 0.42 density on the print films. The pack it wants is still published, so the honest answer is to say so. Report no usable pack and let the module offer to fetch it, exactly as for an edit whose table is missing outright. The download asks for the edit's table rather than for nothing: the manifest is ordered oldest first, so asking for the table lands on the pack the edit was made with, where asking for nothing lands on whatever is flagged default -- which need not be the same pack, and will not be once a newer one takes over. An edit that has since chosen a table itself is not pinned this way and is left alone. * offer the pack that carries the table an edit chose An edit rendering on a non-default table could not be rescued. Opened where that table is not installed it reported that it cannot be rendered, and the row that would have fetched the pack stayed hidden -- while the update check found the very same pack and installing it fixed everything. Choosing a table moves an edit to whichever pack supplies it, and releases the recorded pack and table so resolution follows the choice. Such an edit therefore carries upsampling_hash and a lut_hash of 0. The data row read lut_hash alone, so it resolved to whatever was installed, called it an exact match and concluded nothing was missing. Read the table the edit actually needs -- the chosen one where there is one -- and look for it by every table a pack declares rather than by the one in its header, which names only the default. The download then asks for that table, and the manifest match walks the same declarations. An arctic edit with only 0.3.3 installed now offers 0.3.4. * one lookup for which pack an edit needs Three hashes answer three different questions -- a table the edit was moved to, the pack it was developed against, the table it was stamped with -- and they are consulted in priority order. The renderer read all three; the module's data row read one, then two. Every time they disagreed the result was the same shape of bug: an edit the renderer refused while the row reported nothing missing, or a pack fetched that was not the one wanted. Three fixes in, the disagreement was still the underlying fault rather than any of its symptoms. _edit_pack_dir() answers it once, for both, and names the four outcomes worth telling apart: the pack is here, a substitute is here, a pre-identity edit has lost its pre-identity pack, or there is nothing. It also reports the table to ask a download for. No behaviour change intended beyond the removed disagreement: the order is the renderer's existing one, and _legacy_pack_unavailable() goes away, its answer now being one of the four states.
* offer to check the repository for a newer data pack Downloading was only ever offered when resolution failed: nothing installed, or installed but not the table this edit wants. A pack already satisfying every edit on the machine could be years behind and nothing would say so, the module having no reason to read the manifest. sf_fetch_check_start() runs the existing worker as far as the manifest and stops, reporting through the same status the download uses. Newer means the published default pack is not installed under its own identity; a pack declaring none cannot be compared and reports nothing to do, since fetching it again would change nothing. The data row, idle until now whenever resolution succeeded, carries the button. Offered rather than run on its own, because it is network traffic nobody asked for, and because a check that found something would otherwise have to decide what to do about it. Nothing is adopted behind an existing edit: a fetched pack installs beside the one you have, and only a fresh edit or a deliberate table choice renders on it. That is what makes the button safe to offer at all -- before an edit could name the pack it was made with, the same button would have quietly changed how existing work rendered. * name the pack version when offering or fetching one The download row said only "download data pack", and the progress line only which file was in flight. Neither said which pack, so there was no way to tell from the UI whether the repository was offering the release you expected -- the one thing worth knowing before accepting a pack that brings revised profiles. The manifest carries spektrafilm_version per entry and the worker had it all along. Record it for the entry actually chosen, report it through sf_fetch_available_version(), and use it in the button, the status line and the per-file progress message: "download data pack 0.3.4", "downloading pack 0.3.4: profiles/kodak_gold_200.json (7/34)". A label and never an identity. Two packs can report one version -- the 0.3.3 and 0.3.4 trees both did, from an editable install whose metadata predated the bump -- which is exactly why selection goes by pack_hash and this string only ever gets shown. * check every published pack, and move the check under advanced The check asked _parse_manifest() for the entry a download would take, which is the default pack. During a rollout the default is deliberately the older one, so the question "is anything newer published" was put to the pack least likely to be. Against a repository publishing 0.3.3 as default and 0.3.4 beside it, the check reported that the published pack declares no identity -- true of 0.3.3, and beside the point. Scan every entry instead, skipping ones this darktable could not load and ones declaring no identity, and report the last not installed; the manifest is emitted oldest first, so that is the newest. A completed check also no longer announces itself as a download failure. It shared the worker's finish path, which knows only installed or failed, and a check that finds nothing is neither. The answer belongs in the row beside the button that asked, not in a toast. That row is now its own control under advanced. The row at the top of the module reports a pack missing right now, which belongs there because nothing renders without it; asking whether a newer one exists is housekeeping and does not. * log the manifest url the check actually read The repository and ref are both configurable and both default to upstream's, so a check reporting nothing new is ambiguous: a repository with nothing newer and a ref pointing at the wrong branch produce the same answer, and nothing recorded which was read. Log it beside the pack-resolution line that already names the directory chosen. * say what the update check decided about each pack The check reports one line for a whole manifest, so a wrong answer gives nothing to work back from: a pack skipped for its format, for declaring no identity, or for looking installed are indistinguishable from a repository with nothing new. Log the decision per entry, and say so when the manifest does not parse rather than treating it as an empty one. * refresh the update row once the check has answered The check runs on the worker thread, but pressing the button painted the row straight away and never again: the poll that repaints during a fetch is started inside _update_data_row(), which the update button does not call. So the row kept the answer it had before the check began -- "the installed data packs are up to date", from the moment nothing was known yet -- while the worker went on to find a newer pack and record it. Start the same timer the download path uses. _update_data_row() clears it once the fetch stops running, and _data_poll_cb repaints both rows on every tick, so the answer lands as soon as it exists. * finish the update row -- last in advanced, own progress, installed state Three things the row got wrong once it started working. It sat above the gamut compression toggle, in the middle of a section of render controls. It is housekeeping and the only control there that talks to the network, so it goes last. A fetch started from it reported progress in the row at the top of the module, which is about a pack missing right now -- neither missing nor urgent describes an update. The row now carries its own progress and the top row stays hidden for a fetch this one started. The bar is laid over the button rather than placed under it: the row is three lines in a narrow panel and a fourth that exists only mid-fetch makes the section jump. A thin strip along the button's bottom edge reads as the button filling, and the theme's progressbar colors apply -- a css gradient on the button could not reach them, @-colors being private to the provider that defines them. It is pass-through, so clicks belong to the button underneath. And a pack that had just been downloaded was still offered for download, nothing clearing the available hash on install. The row asks the disk instead, and says installed once it is there. * rebuild the module when a pack fetched from the update row lands Downloading 0.3.4 left the spectral upsampling selector showing the one table the old pack had, until the image was left and re-entered. _update_data_row() rebuilds the GUI on the running-to-done transition, which is what rescans the profiles and the table list. A fetch started from the update row returned before recording that it was running, so the transition never happened: the fetch finished, the pack was in place, and the module kept the list it had read before the download. Record the state before returning. Re-entering the image worked because it re-runs gui_update() by another route.
* expose the scanner black and white point A slide carries base density and never reaches d-max, so a scan of one is washed out until its endpoints are placed. The module placed them and said nothing: the levels were compiled in at 0.98 and 0.01 and the fit ran whenever a positive stock was scanned, with no way to move either end. The reference has four controls here -- scanner.black_correction, white_correction and their levels -- so take those. The levels are sRGB-encoded display values as upstream states them, decoded on use; 0.01 and 0.98 decode to exactly the constants that were compiled in, so nothing renders differently until a slider is moved. legacy_params() sets the same, and both corrections default on, which is what the module already did. One correction on its own moves only its own endpoint, the other being held where the film puts it, so the corrected end still lands exactly where asked ([cr] _correction_fucntion). On the scanner tab between output and sharpness, which is the order the scan stage applies them in: boost, then the endpoint fit, then blur and unsharp, then viewing glare. Insensitive unless a positive stock is being scanned -- a print has its own paper white and black. * place the black and white point on a print too Only a scan of positive film had its endpoints placed. Upstream corrects a second case ([cr] the `not scan_film and print is negative` branch): a print on negative-type paper, whose range is set by the film behind it rather than by the paper, so the paper never reaches its own black or white and the print is flat until the ends are put somewhere. Every paper in the pack is negative-type, so this was every print -- and with the controls gated on the scan case, the four sliders were inert for anyone not scanning slides. The endpoints are the film at its extremes, its fog floor and its D-max ([st] printing.expose), carried through the enlarger and developed on the paper exactly as a pixel is. They cross over on the way: the film's thinnest point passes the most light and prints darkest, so it is the print's black. Same affine fit as the scan case, so one correction on its own still moves only its own endpoint. A paper and exposure that land both ends together leave the correction off rather than fit an arbitrary slope. The GUI gate follows: sensitive for a positive-film scan or a print on negative paper, inert for a negative scan and for positive paper, which carry their own endpoints.
piratenpanda
force-pushed
the
spektrafilm_packv3
branch
from
September 21, 2026 05:27
30cf119 to
09f50ac
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.
Summary
This PR is a WIP to prepare spektrafilm for the new arctic upsampling. This is under heavy development so don't test this yet please.
Current features:
To-Do
Checklist
coding style.
own.
src/tests/integration/where thepixelpipe is touched, or
darktable-clias a headless smoke test._(), new preferences are registered indata/darktableconfig.xml.in.RELEASE_NOTES.mdentry was added (only needed if fixing an issue in a release). Do not reference GitHub issues.Test instructions
not yet done, do not test
AI assistance
Co-created with Claude Opus 5