perf(photos): ускорить рендер правок в 9 раз
Расчёт правок считал math.Pow для каждого канала каждого пикселя, из-за чего большое фото обрабатывалось больше секунды. Факторы правок теперь считаются один раз на рендер, квадраты считаются умножением, а строки обрабатываются параллельно. Обработка кадра 4000×3000 сократилась с 1.23 с до 0.14 с. Задержка от сохранения правок до появления отредактированной версии в сетке и просмотре: около 0.15 с для небольшого фото и 0.6 с для большого (полноразмерная версия догружается следом, через 1.4 с).
This commit is contained in:
@@ -212,31 +212,58 @@ func curveEval(points []CurvePoint, x float64) float64 {
|
||||
return last.Y
|
||||
}
|
||||
|
||||
// adjustChannel applies the light adjustments to a single channel value and
|
||||
// then maps it through the tone curves: the master curve first, the channel
|
||||
// curve second. The order of the operations is fixed and mirrored by the
|
||||
// client-side preview, so the live preview and the rendered result match.
|
||||
func adjustChannel(v float64, p LightParams, master, channel *[256]uint8) float64 {
|
||||
// lightAdjuster holds the precomputed factors of the light adjustments. The
|
||||
// factors are calculated once per render, so the per-pixel loop stays cheap.
|
||||
type lightAdjuster struct {
|
||||
exposure float64
|
||||
contrast float64
|
||||
highlights float64
|
||||
shadows float64
|
||||
whites float64
|
||||
blacks float64
|
||||
}
|
||||
|
||||
func newLightAdjuster(p LightParams) lightAdjuster {
|
||||
return lightAdjuster{
|
||||
exposure: math.Pow(2, p.Exposure),
|
||||
contrast: 1 + p.Contrast/100,
|
||||
highlights: p.Highlights / 100 * 80,
|
||||
shadows: p.Shadows / 100 * 80,
|
||||
whites: p.Whites / 100 * 80,
|
||||
blacks: p.Blacks / 100 * 80,
|
||||
}
|
||||
}
|
||||
|
||||
// apply maps a single channel value through the adjustments and the tone
|
||||
// curves: the master curve first, the channel curve second. The order of the
|
||||
// operations is fixed and mirrored by the client-side preview, so the live
|
||||
// preview and the rendered result match.
|
||||
func (a lightAdjuster) apply(v float64, master, channel *[256]uint8) float64 {
|
||||
// Экспозиция (стопы).
|
||||
v *= math.Pow(2, p.Exposure)
|
||||
v *= a.exposure
|
||||
// Контраст вокруг средней точки.
|
||||
k := 1 + p.Contrast/100
|
||||
v = 128 + (v-128)*k
|
||||
v = 128 + (v-128)*a.contrast
|
||||
// Света: верхняя часть диапазона.
|
||||
w := math.Pow(clamp01((v-128)/127), 2)
|
||||
v += p.Highlights/100*80*w
|
||||
w := clamp01((v - 128) / 127)
|
||||
v += a.highlights * w * w
|
||||
// Тени: нижняя часть диапазона.
|
||||
w = math.Pow(clamp01((128-v)/128), 2)
|
||||
v += p.Shadows/100*80*w
|
||||
w = clamp01((128 - v) / 128)
|
||||
v += a.shadows * w * w
|
||||
// Белые: самый верх диапазона.
|
||||
w = clamp01((v - 192) / 63)
|
||||
v += p.Whites/100*80*w
|
||||
v += a.whites * w
|
||||
// Чёрные: самый низ диапазона.
|
||||
w = clamp01((64 - v) / 64)
|
||||
v += p.Blacks/100*80*w
|
||||
v += a.blacks * w
|
||||
// Тоновая кривая: общая, затем канальная.
|
||||
idx := clampInt(int(math.Round(v)), 0, 255)
|
||||
idx := clampInt(int(v+0.5), 0, 255)
|
||||
v = float64(master[idx])
|
||||
idx = clampInt(int(math.Round(v)), 0, 255)
|
||||
return clampFloat(float64(channel[idx]), 0, 255)
|
||||
idx = clampInt(int(v+0.5), 0, 255)
|
||||
return float64(channel[idx])
|
||||
}
|
||||
|
||||
// adjustChannel applies the light adjustments to a single channel value. It
|
||||
// is the per-pixel helper of the renderer.
|
||||
func adjustChannel(v float64, p LightParams, master, channel *[256]uint8) float64 {
|
||||
return newLightAdjuster(p).apply(v, master, channel)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user