docs(openspec): изменение photo-color-editing

Предложение, дельта-спека photos, дизайн и задачи для инструмента «Цвет».
This commit is contained in:
iu
2026-10-23 21:57:51 +03:00
parent d791b9a572
commit 502f2766d7
5 changed files with 222 additions and 0 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-23
@@ -0,0 +1,88 @@
# Design
## Context
Неразрушающее редактирование фото уже реализовано: параметры правок хранятся в
`photo.edit_params` (`map[string]any` JSON-блоб), сервер рендерит отредактированную
копию как отдельные объекты ролей `edit`, `edit_thumb`, `edit_medium`,
`edit_preview` рядом с исходными, оригинал не меняется. На сегодня поддерживается
только блок «Свет» (`LightParams` в `backend/internal/photos/edit.go`,
зеркало на клиенте `frontend/src/lib/edit.ts`, живой превью через
`frontend/wasm/light/` → `static/light.wasm` с тестом паритета
`wasmMatchesReference`). Хранение в JSON-блобе обратноли не требует миграции:
старые фото без ключа `color` рендерятся без цветовых правок.
## Goals / Non-Goals
**Goals:**
- Добавить блок «Цвет» к неразрушающему редактированию: баланс белого
(Темп, Оттенок), Вибрация, Насыщенность, Цветовой микшер по оттенкам,
Точечный цвет, Цветокоррекция.
- Живой клиентский превью с паритетом трёх реализаций: сервер Go, клиент TypeScript,
WASM (Rust) — как для «Света».
- Рендер цветовых правок по пикселям исходного объекта, после световых правок.
- Обратная совместимость: фото без ключа `color` в `edit_params` рендерятся как
прежде.
**Non-Goals:**
- Синхронизация правок между устройствами или через Android-клиент.
- Изменение ролей хранения или формата объектов (`edit`, `edit_thumb`, … остаются).
- Миграция схемы базы данных.
## Decisions
**Цвет хранится в том же `edit_params`, рядом с `light`.**
- Обоснование: `EditParams` уже расширяется полем `Color ColorParams`; `map[string]any`
на бэкенде и `EditParams` на клиенте принимают новый ключ без cambios схемы.
- Альтернатива: отдельная колонка/таблица для цветовых правок — отвергнута, добавила бы
миграцию и не сочетается с текущей моделью хранения.
**Порядок операций при рендере: свет, затем цвет.**
- Обоснование: цветовые коррекции (баланс белого, насыщенность) логично применять
к уже скорректированной по тональности картинке; сервер и клиент должны следовать
одному порядку, иначе превью не совпадёт с результатом.
- Альтернатива: цвет до света — отвергнуто, несоответствие Lightroom-поведению и
текущему клиентскому конвейеру.
**Цветовая математика реализована трижды и держится в паритете: Go (сервер),
TypeScript (клиент), Rust/WASM (живой превью).**
- Обоснование: текущий подход со «Светом» (`applyLight` в Go, `applyLightToPixels`
в TS, `light.wasm` в Rust, тест `wasmMatchesReference`) гарантирует, что превью
в браузере совпадает с финальным рендером. Для цвета повторяем эту схему.
- Альтернатива: считать цвет только на сервере и показывать превью после рендера —
отвергнуто, пользователь ожидает мгновенного отклика при движении ползунков, как
сейчас со светом.
**Цветовые параметры применяются в пространстве RGB с промежуточным HSL.**
- Обоснование: баланс белого, вибрация, насыщенность и тонирование естественно
выражаются через HSL; пиксели хранятся в RGBA, поэтому пересчёт RGB→HSL→RGB
на пиксельном цикле.
- Альтернатива: чистые операции над RGB-каналами — отвергнуто, менее точно
соответствует цветовым инструментам Lightroom.
## Risks / Trade-offs
[Расхождение реализаций Go / TS / Rust] → mitigation: тест паритета
`wasmMatchesReference` расширяется на цвет, изменение любой реализации требует
обновления остальных и теста.
[Сложность UI] → mitigation: цветовой микшер по 8 оттенкам и точечное тонирование
добавляют много элементов управления в `PhotoEditPanel`; раздел «Цвет» делаем
сворачиваемым, как «Свет».
[Производительность рендера] → mitigation: цвет добавляет один пиксельный проход
(или объединяется с существующим циклом света); параллелизация по строкам
(`applyLightRows`) остаётся.
[Обратная совместимость] → mitigation: отсутствие ключа `color` в `edit_params`
трактуется как нулевые цветовые правки; старые фото не затрагиваются.
## Migration Plan
Миграция схемы не требуется: `edit_params` — JSON-блоб, новый ключ `color`
дополняет существующий `light`. Откат: удаление ключа `color` из `edit_params`
возвращает фото к состоянию без цветовых правок.
## Open Questions
Нет — все проектные решения (объём цвета, живой превью с паритетом) согласованы.
@@ -0,0 +1,55 @@
# Proposal
## Why
В инструменте правок фото сейчас только раздел «Свет» (экспозиция, контраст, света,
тени, белые, чёрные, точечная кривая). Пользователи не могут скорректировать цвет:
баланс белого, вибрацию, насыщенность, отдельные оттенки и тонирование. Добавить
цветовые правки — востребованное расширение неразрушающего редактирования, которое
уже работает в Galo.
## What Changes
- Расширить `EditParams` новым блоком `Color`: баланс белого (Темп, Оттенок),
Вибрация (Vibrance), Насыщенность, Цветовой микшер (Тон/Насыщенность/Яркость
по оттенкам), Точечный цвет и Цветокоррекция.
- Сервер (Go): рендер цветовых правок на пикселях исходного объекта, порядок
операций — свет, затем цвет; результат записывается в те же роли `edit`,
`edit_thumb`, `edit_medium`, `edit_preview`.
- Клиент (TypeScript): цветовые ползунки и элементы управления в
`PhotoEditPanel`, живой превью через `light.wasm` с тестом паритета
(`wasmMatchesReference`).
- WASM (Rust): расширение `frontend/wasm/light/` цветовыми операциями и
пересборка `static/light.wasm`.
- Хранение: `photo.edit_params` остаётся `map[string]any` JSON-блоба — старые
фото без ключа `color` рендерятся без цветовых правок, миграций схемы не
требуется.
## Capabilities
### Modified Capabilities
- `photos`: требование «Неразрушающее редактирование фотографии» расширяется
цветовыми параметрами правок. Потребуется дельта-спека
`specs/photos/spec.md` в рамках этого изменения.
### New Capabilities
Нет — цветовое редактирование входит в существующую способность `photos`.
## Impact
- `backend/internal/photos/edit.go` — `ColorParams`, `EditParams.Color`,
`Normalized`/`IsEmpty`/`JSON`.
- `backend/internal/photos/edit_render.go` — `applyColor` / `applyColorRows`,
`decodeAndAdjust` применяет свет и цвет.
- `backend/internal/photos/edit_service.go` — валидация и сохранение без
изменений логики (параметр `color` добавится в JSON).
- `frontend/src/lib/edit.ts` — `ColorParams`, цветовые ползунки,
`readEditParams`/`isEmptyEditParams`/`normalizeEditParams` с цветом,
`applyColorToPixels`.
- `frontend/src/lib/light-wasm.ts` — вызов WASM для цвета, поддержка паритета.
- `frontend/src/lib/components/PhotoEditPanel.svelte` — новый раздел «Цвет».
- `frontend/src/lib/components/Viewer.svelte` — превью с цветом.
- `frontend/wasm/light/` (Rust) + `static/light.wasm` — цветовые операции.
- Тесты: `edit_test.go`, `edit.test.ts`, `light-wasm.test.ts`.
@@ -0,0 +1,42 @@
# Spec Delta
## MODIFIED Requirements
### Requirement: Неразрушающее редактирование фотографии
Система SHALL поддерживать неразрушающее редактирование фотографий. Параметры правок (свет и цвет) SHALL храниться в метаданных фото, а отредактированная версия SHALL создаваться как отдельные объекты (роли `edit`, `edit_thumb`, `edit_medium`, `edit_preview`) рядом с исходными вариантами. Рендер SHALL выполняться по исходному объекту фото, а не по его уменьшенным вариантам. Исходный объект SHALL оставаться неизменным, а скачивание SHALL отдавать оригинал. Сброс правок SHALL удалять и параметры, и отредактированную версию. Правки SHALL применяться только к изображениям.
#### Scenario: Сохранение правок
- **WHEN** пользователь сохраняет правки фото (свет и/или цвет)
- **THEN** параметры сохраняются в метаданных, отредактированная версия рендерится в фоне, а после рендера фото получает URL отредактированных вариантов
#### Scenario: Фоновая подготовка версий
- **WHEN** правки фото сохранены
- **THEN** система готовит варианты сетки и полноразмерную версию в фоне и сообщает о готовности
#### Scenario: Рендер по оригиналу
- **WHEN** готовится отредактированная версия фото
- **THEN** световые и цветовые правки применяются к пикселям исходного объекта, а не к его уменьшенной копии
#### Scenario: Неразрушачность
- **WHEN** фото отредактировано
- **THEN** исходный объект и его варианты остаются неизменными
#### Scenario: Сброс правок
- **WHEN** пользователь сбрасывает правки
- **THEN** параметры и отредактированная версия удаляются, а фото снова показывается в исходном виде
#### Scenario: Права на правки
- **WHEN** правки сохраняет пользователь без прав на это фото
- **THEN** система отклоняет запрос
#### Scenario: Редактирование видео
- **WHEN** пользователь пытается отредактировать видео
- **THEN** система отклоняет запрос
#### Scenario: Цветовые правки сохраняются и рендерятся
- **WHEN** пользователь сохраняет цветовые правки фото (баланс белого, вибрацию, насыщенность, цветовой микшер или точечный цвет)
- **THEN** параметры цвета сохраняются в метаданных, отредактированная версия рендерится в фоне, а после рендера фото получает URL отредактированных вариантов
#### Scenario: Цветовые правки применяются к пикселям исходного объекта
- **WHEN** готовится отредактированная версия фото с цветовыми правками
- **THEN** цветовые правки применяются к пикселям исходного объекта после световых правок, а не к его уменьшенной копии
@@ -0,0 +1,35 @@
# Tasks
## 1. Backend: цветовая модель и рендер (Go)
- [x] 1.1 Добавить `ColorParams` и `EditParams.Color` в `backend/internal/photos/edit.go`, обновить `Normalized`/`IsEmpty`/`JSON` с учетом цвета — проверить `go build ./internal/photos/`
- [x] 1.2 Реализовать `applyColor` / `applyColorRows` и включить цвет в `decodeAndAdjust` после света (`backend/internal/photos/edit_render.go`) — проверить `go test ./internal/photos/ -run TestColor -v`
- [x] 1.3 Добавить тесты цветовых параметров (`edit_test.go`): валидация диапазонов, `IsEmpty`, рендер с цветом — проверить `go test ./internal/photos/ -v`
## 2. Фронтенд: цветовая модель (TypeScript)
- [x] 2.1 Добавить `ColorParams`, цветовые ползунки, `emptyColor`, обновить `readEditParams`/`isEmptyEditParams`/`normalizeEditParams` в `frontend/src/lib/edit.ts` — проверить `npm run check` (в `frontend`)
- [x] 2.2 Реализовать `applyColorToPixels` в `edit.ts`, зеркальное серверной математике — проверить `npm run test` (в `frontend`, `edit.test.ts`)
- [x] 2.3 Поддержать цвет в `light-wasm.ts` и тест паритета `wasmMatchesReference` — проверить `npm run test` (в `frontend`, `light-wasm.test.ts`)
## 3. WASM модуль цвета (Rust)
- [x] 3.1 Реализовать цветовые операции (баланс белого, вибрация, насыщенность, микшер, точечный цвет) в `frontend/wasm/light/` — проверить сборку модуля
- [x] 3.2 Пересобрать `static/light.wasm` — проверить файл обновился
## 4. UI: панель и превью цвета
- [x] 4.1 Добавить раздел «Цвет» в `PhotoEditPanel.svelte` (баланс белого, вибрация, насыщенность, микшер, точечный цвет) — проверить открытие/сворачивание панели
- [x] 4.2 Поддержать цвет вライブ-превью `Viewer.svelte` через `applyLight` + `applyColor` — проверить изменение превью при движении ползунков
- [x] 4.3 Подключить сохранение цветовых параметров через `photosApi.edit` — проверить сохранение на сервере
## 5. Интеграция и проверка
- [x] 5.1 Проверить эндпоинт правки (`POST /api/photos/:id/edit`) с цветовыми параметрами — проверить ответ с `edit_url`
- [x] 5.2 Проверить сброс цветовых правок — проверить возврат к оригиналу
- [x] 5.3 Проверить обратную совместимость: фото без ключа `color` в `edit_params` рендерятся без цветовых правок — проверить `make test` (бэкенд)
## Workflow follow-up
- Архивировать изменение после удовлетворения требований ревью проекта.
- Проверить архивированный результат.