From 6cfd42c511e2b8a9c73c80bbf861ce90ba23d8a4 Mon Sep 17 00:00:00 2001 From: melchiaaronzedek Date: Thu, 15 Oct 2026 15:41:57 +0530 Subject: [PATCH] M8: Fujifilm F-Log, F-Log2 and F-Log2 C colour spaces (F-Gamut / F-Gamut C) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Interpret Footage ▸ Color Management now offers "Fujifilm F-Log / F-Gamut", "Fujifilm F-Log2 / F-Gamut" and "Fujifilm F-Log2 C / F-Gamut C" (X-S20, X-H2S, X-T5, GFX…), implemented from Fujifilm's GFX ETERNA Technical Guide white paper v1.01 (the F-Log / F-Log2 data sheet formulas): 10-bit code-value domain, F-Gamut = BT.2020 primaries, F-Gamut C primaries (0.7347, 0.2653) (0.0263, 0.9737) (0.1173, -0.0224). Round trip and monotonicity tests cover the new curves with the others; reference values from the white paper's table: 0 % → 95, 18 % → 470 / 400, 90 % → 705 / 570 (F-Log / F-Log2). Co-Authored-By: Claude Opus 5.5 --- crates/color/README.md | 2 ++ crates/color/src/log.rs | 63 ++++++++++++++++++++++++++++++++++++-- crates/color/src/spaces.rs | 27 ++++++++++++++-- docs/roadmap.md | 4 +-- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/crates/color/README.md b/crates/color/README.md index 32c3edee8..e78774322 100644 --- a/crates/color/README.md +++ b/crates/color/README.md @@ -38,6 +38,8 @@ for limited- and full-range files. | ARRI LogC4 | ARRI Wide Gamut 4 | ARRI, *ARRI LogC4 Logarithmic Color Space — Specification* (2022) | CV | a = (2¹⁸ − 16)/117.45, b = 928/1023, c = 95/1023, s = 7·ln2·2^(7 − 14c/b)/(a·b), t = (2^(6 − 14c/b) − 64)/a; y ≥ 0: x = (2^(14(y − c)/b + 6) − 64)/a; else x = y·s + t | | Apple Log | BT.2020 | Apple, *Apple Log Profile* white paper (2023) | IRE | R0 = −0.05641088, Rt = 0.01, c = 47.28711236, β = 0.00964052, γ = 0.08550479, δ = 0.69336945; P ≥ c(Rt − R0)²: x = 2^((P − δ)/γ) − β; else x = √(P/c) + R0 | | DJI D-Log | D-Gamut | DJI, *White Paper on D-Log and D-Gamut of DJI Cinema Color System* (2017) | CV | y ≤ 0.14: x = (y − 0.0929)/6.025; else x = (10^(3.89616y − 2.27752) − 0.0108)/0.9892 | +| Fujifilm F-Log | F-Gamut (= BT.2020 primaries) | Fujifilm, *GFX ETERNA Technical Guide – White Paper* v1.01 (2026), F-Log conversion formula (same as the F-Log data sheet) | CV | y ≥ cut2: x = 10^((y − d)/c)/a − b/a; else x = (y − f)/e; a 0.555556, b 0.009468, c 0.344676, d 0.790453, e 8.735631, f 0.092864, cut1 0.00089, cut2 0.100537775223865 | +| Fujifilm F-Log2 / F-Log2 C | F-Gamut / F-Gamut C (R 0.7347, 0.2653; G 0.0263, 0.9737; B 0.1173, −0.0224) | same document, F-Log2 C / F-Log2 conversion formula | CV | same form; a 5.555556, b 0.064829, c 0.245281, d 0.384316, e 8.799461, f 0.092864, cut1 0.000889, cut2 0.100686685370811 | The encoders are the exact inverses given in the same documents. DJI **D-Log M** is not implemented: DJI has not published its formula (only conversion LUTs), and we do not derive curves diff --git a/crates/color/src/log.rs b/crates/color/src/log.rs index 7fd23afa8..ec5967518 100644 --- a/crates/color/src/log.rs +++ b/crates/color/src/log.rs @@ -42,11 +42,25 @@ pub enum LogCurve { AppleLog, /// DJI D-Log. DLog, + /// Fujifilm F-Log. + FLog, + /// Fujifilm F-Log2 (also the curve of F-Log2 C). + FLog2, } impl LogCurve { - pub const ALL: [LogCurve; 8] = - [LogCurve::SLog3, LogCurve::VLog, LogCurve::CLog2, LogCurve::CLog3, LogCurve::LogC3, LogCurve::LogC4, LogCurve::AppleLog, LogCurve::DLog]; + pub const ALL: [LogCurve; 10] = [ + LogCurve::SLog3, + LogCurve::VLog, + LogCurve::CLog2, + LogCurve::CLog3, + LogCurve::LogC3, + LogCurve::LogC4, + LogCurve::AppleLog, + LogCurve::DLog, + LogCurve::FLog, + LogCurve::FLog2, + ]; pub fn domain(self) -> Domain { match self { @@ -65,6 +79,8 @@ impl LogCurve { LogCurve::LogC4 => "ARRI LogC4", LogCurve::AppleLog => "Apple Log", LogCurve::DLog => "DJI D-Log", + LogCurve::FLog => "Fujifilm F-Log", + LogCurve::FLog2 => "Fujifilm F-Log2", } } @@ -79,6 +95,8 @@ impl LogCurve { LogCurve::LogC4 => logc4_encode(x), LogCurve::AppleLog => apple_log_encode(x), LogCurve::DLog => dlog_encode(x), + LogCurve::FLog => fujifilm_encode(&FLOG, x), + LogCurve::FLog2 => fujifilm_encode(&FLOG2, x), } } @@ -93,6 +111,8 @@ impl LogCurve { LogCurve::LogC4 => logc4_decode(y), LogCurve::AppleLog => apple_log_decode(y), LogCurve::DLog => dlog_decode(y), + LogCurve::FLog => fujifilm_decode(&FLOG, y), + LogCurve::FLog2 => fujifilm_decode(&FLOG2, y), } } @@ -262,6 +282,35 @@ pub fn dlog_decode(y: f64) -> f64 { if y <= 0.14 { (y - 0.0929) / 6.025 } else { (10f64.powf(3.896_16 * y - 2.277_52) - 0.0108) / 0.9892 } } +// ---------------------------------------------------------------- Fujifilm F-Log / F-Log2 +// Fujifilm, "GFX ETERNA Technical Guide - White Paper" v1.01 (2026), F-Log / F-Log2 C / F-Log2 +// conversion formulas (the same as the F-Log and F-Log2 data sheets). `in` is scene-linear +// reflection, `out` 10-bit code value / 1023: 0 % → 95, 18 % → 470 (F-Log) / 400 (F-Log2). + +/// a, b, c, d, e, f, cut1 (encoding: scene-linear), cut2 (decoding: signal). +struct Fujifilm { + a: f64, + b: f64, + c: f64, + d: f64, + e: f64, + f: f64, + cut1: f64, + cut2: f64, +} + +const FLOG: Fujifilm = + Fujifilm { a: 0.555_556, b: 0.009_468, c: 0.344_676, d: 0.790_453, e: 8.735_631, f: 0.092_864, cut1: 0.000_89, cut2: 0.100_537_775_223_865 }; +const FLOG2: Fujifilm = + Fujifilm { a: 5.555_556, b: 0.064_829, c: 0.245_281, d: 0.384_316, e: 8.799_461, f: 0.092_864, cut1: 0.000_889, cut2: 0.100_686_685_370_811 }; + +fn fujifilm_encode(k: &Fujifilm, x: f64) -> f64 { + if x >= k.cut1 { k.c * (k.a * x + k.b).log10() + k.d } else { k.e * x + k.f } +} +fn fujifilm_decode(k: &Fujifilm, y: f64) -> f64 { + if y >= k.cut2 { 10f64.powf((y - k.d) / k.c) / k.a - k.b / k.a } else { (y - k.f) / k.e } +} + #[cfg(test)] mod tests { use super::*; @@ -317,6 +366,16 @@ mod tests { // DJI D-Log: 0 → 0.0929, 18 % → 0.398 (white paper). close(LogCurve::DLog.encode(0.0), 0.0929, 1e-9, "D-Log 0"); close(LogCurve::DLog.encode(0.18), 0.398, 0.002, "D-Log 18%"); + // Fujifilm (white paper code-value table): 0 % → 95, 18 % → 470 / 400, 90 % → 705 / 570. + close(cv(LogCurve::FLog, 0.0), 95.0, 0.5, "F-Log 0%"); + close(cv(LogCurve::FLog, 0.18), 470.0, 0.5, "F-Log 18%"); + close(cv(LogCurve::FLog, 0.9), 705.0, 0.6, "F-Log 90%"); + close(cv(LogCurve::FLog2, 0.0), 95.0, 0.5, "F-Log2 0%"); + close(cv(LogCurve::FLog2, 0.18), 400.0, 0.5, "F-Log2 18%"); + close(cv(LogCurve::FLog2, 0.9), 570.0, 0.5, "F-Log2 90%"); + // the decoding cut is where the encoding's two pieces meet (within the published rounding) + close(LogCurve::FLog.encode(0.000_89), 0.100_537_775_223_865, 2e-4, "F-Log cut"); + close(LogCurve::FLog2.encode(0.000_889), 0.100_686_685_370_811, 2e-4, "F-Log2 cut"); } #[test] diff --git a/crates/color/src/spaces.rs b/crates/color/src/spaces.rs index 0dd93f271..e4a994df1 100644 --- a/crates/color/src/spaces.rs +++ b/crates/color/src/spaces.rs @@ -34,6 +34,8 @@ pub enum Gamut { Awg4, /// DJI D-Gamut. DGamut, + /// Fujifilm F-Gamut C (F-Log2 C). F-Gamut itself has the BT.2020 primaries. + FGamutC, } impl Gamut { @@ -51,6 +53,7 @@ impl Gamut { Gamut::Awg3 => [[0.6840, 0.3130], [0.2210, 0.8480], [0.0861, -0.1020]], Gamut::Awg4 => [[0.7347, 0.2653], [0.1424, 0.8576], [0.0991, -0.0308]], Gamut::DGamut => [[0.71, 0.31], [0.21, 0.88], [0.09, -0.08]], + Gamut::FGamutC => [[0.7347, 0.2653], [0.0263, 0.9737], [0.1173, -0.0224]], } } @@ -165,12 +168,18 @@ pub enum ColorSpace { /// Apple Log (BT.2020 primaries). AppleLog, DLogDGamut, + /// Fujifilm F-Log / F-Gamut (BT.2020 primaries). + FLogFGamut, + /// Fujifilm F-Log2 / F-Gamut (BT.2020 primaries). + FLog2FGamut, + /// Fujifilm F-Log2 C / F-Gamut C. + FLog2CFGamutC, /// Linear light with BT.709 primaries (EXR-style float sources). Linear709, } impl ColorSpace { - pub const ALL: [ColorSpace; 16] = [ + pub const ALL: [ColorSpace; 19] = [ ColorSpace::Rec709, ColorSpace::Srgb, ColorSpace::Rec2020, @@ -186,6 +195,9 @@ impl ColorSpace { ColorSpace::LogC4Awg4, ColorSpace::AppleLog, ColorSpace::DLogDGamut, + ColorSpace::FLogFGamut, + ColorSpace::FLog2FGamut, + ColorSpace::FLog2CFGamutC, ColorSpace::Linear709, ]; @@ -207,6 +219,9 @@ impl ColorSpace { ColorSpace::LogC4Awg4 => "logc4-awg4", ColorSpace::AppleLog => "applelog", ColorSpace::DLogDGamut => "dlog-dgamut", + ColorSpace::FLogFGamut => "flog-fgamut", + ColorSpace::FLog2FGamut => "flog2-fgamut", + ColorSpace::FLog2CFGamutC => "flog2c-fgamutc", ColorSpace::Linear709 => "linear709", } } @@ -228,6 +243,9 @@ impl ColorSpace { ColorSpace::LogC4Awg4 => "ARRI LogC4 / ARRI Wide Gamut 4", ColorSpace::AppleLog => "Apple Log / Rec. 2020", ColorSpace::DLogDGamut => "DJI D-Log / D-Gamut", + ColorSpace::FLogFGamut => "Fujifilm F-Log / F-Gamut", + ColorSpace::FLog2FGamut => "Fujifilm F-Log2 / F-Gamut", + ColorSpace::FLog2CFGamutC => "Fujifilm F-Log2 C / F-Gamut C", ColorSpace::Linear709 => "Linear (Rec. 709 primaries)", } } @@ -242,7 +260,9 @@ impl ColorSpace { pub fn gamut(self) -> Gamut { match self { ColorSpace::Rec709 | ColorSpace::Srgb | ColorSpace::Linear709 => Gamut::Bt709, - ColorSpace::Rec2020 | ColorSpace::Rec2100Pq | ColorSpace::Rec2100Hlg | ColorSpace::AppleLog => Gamut::Bt2020, + ColorSpace::Rec2020 | ColorSpace::Rec2100Pq | ColorSpace::Rec2100Hlg | ColorSpace::AppleLog | ColorSpace::FLogFGamut | ColorSpace::FLog2FGamut => { + Gamut::Bt2020 + } ColorSpace::P3D65 => Gamut::P3D65, ColorSpace::SLog3SGamut3Cine => Gamut::SGamut3Cine, ColorSpace::SLog3SGamut3 => Gamut::SGamut3, @@ -251,6 +271,7 @@ impl ColorSpace { ColorSpace::LogC3Awg3 => Gamut::Awg3, ColorSpace::LogC4Awg4 => Gamut::Awg4, ColorSpace::DLogDGamut => Gamut::DGamut, + ColorSpace::FLog2CFGamutC => Gamut::FGamutC, } } @@ -269,6 +290,8 @@ impl ColorSpace { ColorSpace::LogC4Awg4 => Curve::Log(LogCurve::LogC4), ColorSpace::AppleLog => Curve::Log(LogCurve::AppleLog), ColorSpace::DLogDGamut => Curve::Log(LogCurve::DLog), + ColorSpace::FLogFGamut => Curve::Log(LogCurve::FLog), + ColorSpace::FLog2FGamut | ColorSpace::FLog2CFGamutC => Curve::Log(LogCurve::FLog2), } } diff --git a/docs/roadmap.md b/docs/roadmap.md index 3484e30a2..6f419b182 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap detail -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (new file: milestones moved from ROADMAP.md, Current focus rewritten from the 2026-10-10 re-measure, beta milestones added) · **Target:** Adobe Premiere Pro 2026 (26.5.2) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (M8: Fujifilm F-Log / F-Log2 / F-Log2 C colour spaces) · **Target:** Adobe Premiere Pro 2026 (26.5.2) Forward-looking plan: milestones with status, the current focus and what's next, with estimates in Opus 5.5 agent-hours (calibration in [target-app-parity.md](target-app-parity.md#calibration)). @@ -68,7 +68,7 @@ Status: ✅ done · 🟡 in progress · ⬜ not started. Estimates are remaining | M5 | Effects, keyframes, GPU | 🟡 | All 93 Premiere 26 video effects and 84 transitions (CPU), blend modes and 33 common effects on the GPU (#32, GPU2, GPU4, GPU5), keyframes + value/velocity graphs, wgpu compositor, effect + opacity masks (ellipse/polygon/pen, feather, expansion, modes; CPU/WGSL parity), mask tracking (Lucas–Kanade + RANSAC), adjustment layers, effect presets (built-in + user, JSON import/export) | More effects on the GPU (34 ids done incl. Lumetri basic / creative / vignette; GPU export landed #421), GPU masks in the live path, exact Warp Stabilizer (3-D) and Morph Cut | 40–70 | | M6 | Export | ✅ | Own H.264 encoder (High/Main/Baseline, B-frames, VBR/CBR/2-pass) → MP4 or QuickTime + own AAC; ProRes, DNxHR, MJPEG, PNG/TIFF/BMP sequences (Premiere-style numbering), GIF, WAV, AIFF; background jobs; M6.5: Export mode parity (frame size / rate / scaling / pixel aspect, profile / level, CBR / VBR 1- and 2-pass, keyframe distance, audio codec / rate / channels / bitrate / sample size, multiplexer, burn-in or sidecar captions, image / name / timecode overlays, video limiter, loudness normalization with true-peak limiter, metadata, ranges, estimated size, summary), 24 built-in presets + user presets with favourites and import/export (Preset Manager), export queue (reorder, cancel, retry, several sequences / ranges), Quick Export; `export.*` commands and `filmcraft-cli export --preset` | Interlaced encoding, 5.1 audio, smart render, publishing destinations | 6–8 | | M7 | Audio | 🟡 | Mixer graph (tracks → submixes → Mix, pre/post-fader inserts and sends, latency-compensated, sample-accurate, ~6× realtime for 24 tracks × 3 effects on one core), Audio Track Mixer + Audio Clip Mixer panels, track automation (Off/Read/Latch/Touch/Write, recorded live while playing, thinned to keyframes, timeline lanes with pen editing), solo/solo-safe, channel mapping basics, peak + BS.1770 loudness meters (match ffmpeg), DSP crate with 16 clip/track effects, Audio Gain (set/adjust/normalize), Constant Power / Constant Gain / Exponential Fade, Essential Sound (types, Loudness auto-match, Repair incl. DeEss/DeReverb, Clarity, Creative, Ducking keyframes, presets; full Dialogue chain 22× realtime) | Music duration remix, ML speech enhancement, 5.1 panner and multichannel buses, voice-over record, effect editor windows (EQ curve), remaining effects (multiband, convolution reverb), clip-mixer automation recording | 6–9 | -| M8 | Colour | 🟡 | Lumetri: basic, creative + looks, RGB & hue curves, wheels, HSL secondary, vignette, section bypass; Input LUT / Look LUT (.cube 1D/3D/shaper, .3dl; tetrahedral CPU + WGSL; project LUT library; built-in camera conversions); Colour Match (Oklab tonal-range statistics, skin protection, solved in Lumetri wheels); colour management: Rec. 709 / Rec. 2100 PQ / HLG working spaces + wide gamut, Interpret Footage colour space (S-Log3, V-Log, Canon Log 2/3, LogC3/4, Apple Log, D-Log from published specs), metadata auto-detect, BT.2390 tone mapping, gamut mapping, HDR export signalling (VUI/colr/mdcv/clli/SEI, ffprobe-verified); Lumetri Scopes: Vectorscope YUV (75/100 % targets, skin-tone line) and HLS, Histogram, Parade (RGB/YUV/RGB-White), Waveform (RGB/Luma/YC/YC no Chroma), presets, Rec. 601/709/2100, 8-bit/float/HDR (cd/m²) scales, multi-scope grid, `scopes.read` | HDR-aware Lumetri maths, macOS EDR monitors, mastering metadata → tone-map peak, D-Log M (no published formula), HSL Secondary refine | 3–5 | +| M8 | Colour | 🟡 | Lumetri: basic, creative + looks, RGB & hue curves, wheels, HSL secondary, vignette, section bypass; Input LUT / Look LUT (.cube 1D/3D/shaper, .3dl; tetrahedral CPU + WGSL; project LUT library; built-in camera conversions); Colour Match (Oklab tonal-range statistics, skin protection, solved in Lumetri wheels); colour management: Rec. 709 / Rec. 2100 PQ / HLG working spaces + wide gamut, Interpret Footage colour space (S-Log3, V-Log, Canon Log 2/3, LogC3/4, Apple Log, D-Log, Fujifilm F-Log / F-Log2 / F-Log2 C from published specs), metadata auto-detect, BT.2390 tone mapping, gamut mapping, HDR export signalling (VUI/colr/mdcv/clli/SEI, ffprobe-verified); Lumetri Scopes: Vectorscope YUV (75/100 % targets, skin-tone line) and HLS, Histogram, Parade (RGB/YUV/RGB-White), Waveform (RGB/Luma/YC/YC no Chroma), presets, Rec. 601/709/2100, 8-bit/float/HDR (cd/m²) scales, multi-scope grid, `scopes.read` | HDR-aware Lumetri maths, macOS EDR monitors, mastering metadata → tone-map peak, D-Log M (no published formula), HSL Secondary refine | 3–5 | | M9 | More codecs | 🟡 | ProRes decode+encode, AAC decode+encode, HEVC Main/Main 10 decoder (bit-exact on 41 fixtures, ~225 fps 1080p), VP9 decoder (profiles 0–3, 8/10/12-bit, bit-exact on 50+ fixtures; WebM/MKV `V_VP9` and MP4 `vp09` import with key-frame-checked seeking), Matroska/WebM import (H.264/HEVC/VP9/ProRes/MJPEG + AAC/Opus/FLAC/MP3/Vorbis/PCM), Opus decoder (SILK/CELT/hybrid, 5.1/7.1 multistream; all RFC 8251 vectors range-exact; WebM/MKV/MP4), DNxHD/DNxHR decoder (all SMPTE ST 2019-1 CIDs, 8/10/12-bit, 4:2:2/4:4:4, interlaced, alpha; within IDCT precision of ffmpeg on 22 fixtures) + DNxHR LB/SQ/HQ/HQX/444 encoder and MOV `AVdh` export, AV1 decoder (Main profile, 8/10-bit, all intra/inter tools, loop filters, superres, film grain, intra BC, spatial layers; bit-exact vs libdav1d on 19 SVT fixtures + 22 libaom vectors; MP4 `av01` / WebM `V_AV1` import), MXF import (OP1a/OP-Atom; AVC with POC-ordered B pictures bit-exact, DNxHD/DNxHR, ProRes, PCM/AES3; MPEG-2 reported unsupported; index-table seeking, material package timecode), Ogg Opus/Vorbis (granule seeking, pre-skip, end trimming), image sequences, Broadcast WAV timecode | VP9 frame threading, AV1 threading/SIMD (~4 fps 1080p today), AV1 High/Professional profiles, H.264 High 10 / 4:2:2 and HEVC 4:2:2 in software (G3), E-AC-3, AVI (MPEG-2 decoder and hardware decode have landed) | 30–50 | | M10 | Graphics & captions | 🟡 | Caption tracks (Subtitle/CEA-608/708/Teletext formats, track style), SRT/WebVTT/SCC import+export (frame-exact, property-tested), caption editing (add/split/merge/trim/move, sync-locked insert/extract), Text panel Captions tab, burn-in in Program monitor and export; text engine (`crates/text`: bundled + system fonts, harfrust shaping, bidi, line breaking, paragraph layout, glyph cache; 3-line 1080p title ≈ 0.15 ms warm); graphic clips with text + shape layers (fill, 2 strokes, background, shadow, keyframable transform), Type tool with on-monitor editing, shape/pen tools, Properties/Essential Graphics editor, align/distribute; graphics templates (own `.fcgt` format, 8 original built-ins, Browse tab with engine-rendered thumbnails, export / install / apply / editable properties; never reads .mogrt), rolls/crawls with ease and pre/postroll, responsive pins and intro/outro, per-character styles, Upgrade Caption to Graphic, source graphics, Replace Fonts; MCC (608+708), EBU STL, TTML/IMSC1, DFXP (frame-exact, property-tested) | 608/708 embedding in video streams, speech-to-text | 12–18 | | M11 | Interchange & project management | ✅ | `.fcproj` schema versions + migrations, atomic saves, Save a Copy/Revert, auto-save ring + crash-recovery journal (Preferences ▸ Auto Save, recovery prompt), FCP7 XML, FCPXML, EDL, OTIO; offline media (own slate) + Link Media (fingerprint-checked relink, folder remap, search, Align Timecode, Make Offline); proxies (ProRes Proxy/LT, H.264 ¼/½ background jobs, attach/detach/reconnect, monitor toggle, export full-res) + ingest (copy/transcode/proxies); Project Manager (collect, consolidate + transcode with handles, size estimate) | Rename media to clip names, image-sequence conversion, Media Browser-driven relink, smart (cross-drive) path tracking | 1–2 |