feat(photos): инструмент «Цвет» с WebGL-превью и пипеткой
- неразрушающие правки цвета: баланс белого, вибрация/насыщенность, цветовой микшер, колористика и поканальная коррекция - серверный рендер цвета в Go и паритетная реализация в WASM - живое превью на WebGL2 (кадр считает GPU), CPU/WASM-фолбэк - пипетка баланса белого по нейтральной точке - новые контролы: слайдеры с градиентами, цветовые колёса, двойной клик по контролу сбрасывает значение - тесты: параметры, рендер, паритет JS/WASM, e2e сохранения и сброса
This commit is contained in:
@@ -90,14 +90,68 @@ func applyLightRows(dst *image.NRGBA, from, to int, adj lightAdjuster, master, r
|
||||
}
|
||||
}
|
||||
|
||||
// decodeAndAdjust decodes the original and applies the light adjustments to
|
||||
// it, so the variants and the full-size version share one decode.
|
||||
// decodeAndAdjust decodes the original and applies the light and color
|
||||
// adjustments to it, so the variants and the full-size version share one
|
||||
// decode. Color is applied after light.
|
||||
func decodeAndAdjust(data []byte, p EditParams) (*image.NRGBA, error) {
|
||||
img, err := imaging.Decode(bytes.NewReader(data), imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return applyLight(img, p.Light), nil
|
||||
edited := applyLight(img, p.Light)
|
||||
if p.Color.IsEmpty() {
|
||||
return edited, nil
|
||||
}
|
||||
return applyColor(edited, p.Color), nil
|
||||
}
|
||||
|
||||
// applyColor applies the color adjustments in place to an image that already
|
||||
// has the light adjustments applied. The image is owned by the caller.
|
||||
func applyColor(dst *image.NRGBA, p ColorParams) *image.NRGBA {
|
||||
adj := newColorAdjuster(p)
|
||||
|
||||
rows := dst.Rect.Dy()
|
||||
workers := runtime.GOMAXPROCS(0)
|
||||
if workers > rows {
|
||||
workers = rows
|
||||
}
|
||||
if workers <= 1 {
|
||||
applyColorRows(dst, 0, rows, adj)
|
||||
return dst
|
||||
}
|
||||
// Рендер больших фото идёт по строкам параллельно.
|
||||
chunk := (rows + workers - 1) / workers
|
||||
var wg sync.WaitGroup
|
||||
for from := 0; from < rows; from += chunk {
|
||||
to := from + chunk
|
||||
if to > rows {
|
||||
to = rows
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(from, to int) {
|
||||
defer wg.Done()
|
||||
applyColorRows(dst, from, to, adj)
|
||||
}(from, to)
|
||||
}
|
||||
wg.Wait()
|
||||
return dst
|
||||
}
|
||||
|
||||
// applyColorRows applies the color adjustments to the given range of rows.
|
||||
func applyColorRows(dst *image.NRGBA, from, to int, adj colorAdjuster) {
|
||||
width := dst.Rect.Dx()
|
||||
for y := from; y < to; y++ {
|
||||
row := dst.Pix[y*dst.Stride : y*dst.Stride+width*4]
|
||||
for i := 0; i+3 < len(row); i += 4 {
|
||||
if row[i+3] == 0 {
|
||||
continue
|
||||
}
|
||||
r, g, b := adj.apply(float64(row[i]), float64(row[i+1]), float64(row[i+2]))
|
||||
row[i] = uint8(clampFloat(r+0.5, 0, 255))
|
||||
row[i+1] = uint8(clampFloat(g+0.5, 0, 255))
|
||||
row[i+2] = uint8(clampFloat(b+0.5, 0, 255))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encodeEditedVariants renders the grid variants of an edited photo. They
|
||||
|
||||
Reference in New Issue
Block a user