I understand that darktable can convert RGB color images to monochrome, eg with the color calibration module.
But it is my impression that this just gives me 3 channels which are equal. So even if I keep the image “monochrome” throughout the whole pipeline, all calculations still happen 3 times which is wasteful (yes, D&S, I am looking at you).
Is there a way to just keep one channel to speed up computation?
Monochrome isn’t the same as B&W or grayscale, you can tint a monochrome image (e.g. sepia: monochrome but clearly not grayscale). So if darktable would mark an image converted to grayscale with e.g. colour calibration, you’s be stuck with a grayscale image, with no possibility to tint it.
And, if you use a blend mode and or masks, the grayscale conversion in module may not produce a grayscale image at all.
Afaik, darktable doesn’t have a grayscale mode, like e.g. GIMP has. I think even grayscale images from e.g. png files are converted to RGB on loading, to fit with darktables internal structures.
Adding and using a grayscale mode would need quite a few code changes (with accompanying testing): basic image data structure, GPU handling, module internals, …
Not necessarily. Modules can be classified as adding color or not. Eg the exposure, diffuse and sharpen, sigmoid modules would be grayscale to grayscale (and rgb to rgb), while eg color balance rgb would make mono rgb.
Yes, it is yet another branch in (already complex) code, for a possibly niche use case. In a language like Julia it would be easy to pull of though (just write the pixel to pixel kernels as generic functions, and dispatch on container element type). I suspect C++ too, but unfortunately only a fraction of Darktable is written in that.
Dynamic dispatch comes at a cost: that of checking the type, and finding the right function based on that. Every branch in the code comes with a drop in performance, especially when cache pipelines are taken into account.
There’s also the double maintenance cost.
Sure, but if the element type of the pixel container is known, dispatch would not need to be dynamic. Julia would resolve it at compile time (with idiomatic code), and C++ can do this via templates AFAIK. Modern languages have these kind of zero-cost abstractions.
There is extra cost, but again in a modern language it would not double. Consider eg the exposure module: the GUI is unchanged, the parameters are unchanged, and if you can use static dispatch the actual module code may be unchanged. Consider eg this mockup in Julia:
# color primitives
abstract type Color end
struct RGB{T} <: Color
r::T
g::T
b::T
end
struct Grayscale{T} <: Color
v::T
end
channelwise(f::F, rgb::RGB, x::Real) where F = RGB(f(rgb.r, x), f(rgb.g, x), f(rgb.b, x))
channelwise(f::F, gray::Grayscale, x::Real) where F = Grayscale(f(gray.v, x))
Base.:(*)(color::Color, x::Real) = channelwise(*, color, x)
Base.:(-)(color::Color, x::Real) = channelwise(-, color, x)
# “exposure”
struct Exposure{T}
black::T
scale::T
end
function process!(image::AbstractMatrix{<:Color}, exposure::Exposure)
(; black, scale) = exposure
for i in eachindex(image)
image[i] = (image[i] - black) * scale
end
image
end
which would compile to efficient code regardless of the color representation being RGB or grayscale. That logic is outside the “exposure” implementation and requires no branches.
(With minor modifications the above would parallelize and work fine on the GPU, but I did not do that to avoid distractions.)
And thus began what later came to be known as Juliantable
Joke aside: nice! You are right, dynamic dispatch is more of a problem when calling a method on an unknown (dynamic) type, but here there would only be one branch (selecting the single vs multi-channel algo, depending on the input, at the start of the module); you wouldn’t be calling a method on individual pixels based on the pixel type.
Frankly, I have entertained the idea of a rewrite from scratch, just keeping the key modules (exposure, filmic, D&S, color calibration, tone eq, color balance rgb, …). I imagine it would take about 2000 person-hours to get a prototype. What keeps me from doing it is that 1. I don’t have time, 2. I don’t know color theory that well, 3. I am not experienced with GUI programming.
You could also call it Manó (especially given the every-day Hungarian meaning of the word). Just imagine, the introductory page starting with “Mi a Manó?”.
(For the others: “Mi a manó?” is a funny Hungarian expression expressing mild surprise; “manó” = “imp”, so “Mi a manó?” would mean “What the imp?” (“what has happened here?” → “what have the imps done to this place?”). Another, perhaps more literal, translation would be “What is the imp?”, suitable for a Getting started or About page. Manó Mai was an early Austro-Hungarian photographer.)
Yes, I am aware of vkdt, it has a couple of great design choices (eg filesystem instead of database). Still, I would take a diametrically opposite path: no GPU (at least initially), clean code in Julia, I would be more interested in easy extension and refactoring than raw speed.
Except for D&S, everything is fast enough on the CPU for me already. But there is a ton of stuff where a low barrier to entry would be great for experimentation (global tone mapping, color transformations, “parametric” LUTs). Currently, to contribute to DT, you need to understand a lot of C.