Files
RecipesCam/docker/halo_highlight_shadow_white_black.md
T

135 lines
9.9 KiB
Markdown

# Phân Tích Kỹ Thuật & Giải Pháp Khắc Phục Lỗi "Halo" Khi Chỉnh Highlight, Shadow, White, Black (Tích Hợp Cân Bằng Saturation & Vibrance)
---
## 1. Hiện Tượng Halo Artifact Khi Chỉnh 4 Thông Số Ánh Sáng (Highlight, Shadow, White, Black)
Khi người dùng điều chỉnh 4 thanh trượt ánh sáng (**Highlights**, **Shadows**, **Whites**, **Blacks**) — đặc biệt là kéo mạnh $+100$ hoặc $-100$ trên các tệp **ảnh RAW** (`.NEF`, `.RAF`, `.DNG`, `.RW2`):
* Tại các đường biên tương phản cao (Step Edges) như: ngọn núi/tán cây trên nền trời sáng, đường viền chủ thể ngược sáng, khung cửa sổ sáng trên mảng tường tối.
* Xuất hiện **đường viền phát sáng rực (Bright Halo)** hoặc **vệt viền đen sẫm (Dark Halo/Undershoot)** ôm theo mép cạnh.
* **Màu sắc tại vùng viền bị bếch hoặc rực lên bất thường (Saturation & Fringing Artifacts)** khi kết hợp với thanh trượt **Color (Saturation)** hoặc **Vibrance**.
```
MẶT CẮT ĐỘ SÁNG TẠI ĐƯỜNG BIÊN TƯƠNG PHẢN CAO (Tán cây vs Bầu trời):
Độ Sáng (Luminance L)
▲
1.0 ┤ [Overshoot: Viền sáng rực (Bright Halo)]
│ ┌───┐
0.9 ├────────────────────────────┘ └──────────────────────── (Bầu trời)
│
│
0.1 ├────────────────────────────┐ ┌──────────────────────── (Tán cây)
│ └───┘
0.0 ┴────────────────────────────────────────────────────────► Khoảng cách (X)
[Undershoot: Viền đen sẫm (Dark Halo)]
```
---
## 2. Nguyên Nhân Kỹ Thuật Cốt Lõi
### A. Sự Rò Rỉ Không Gian Của Bộ Lọc Nền (Gaussian Neighborhood Leakage)
1. Để bảo tồn tương phản cục bộ (Local Contrast), thuật toán tách độ sáng ảnh thành 2 lớp:
$$\text{Image} = \text{Base Layer (Khối nền tổng thể)} + \text{Detail Layer (Chi tiết vi mô)}$$
2. Lớp `Base Layer` được trích xuất bằng bộ lọc làm mờ. Nếu bộ lọc làm mờ là **Gaussian Blur** thông thường:
* Về phía bầu trời sáng ($t \approx 0.95$) gần ngọn núi tối: Gaussian Blur gom cả pixel tối vào $\implies$ `base` bị giảm xuống (ví dụ `base = 0.60`).
* Phép tính target độ sáng: $\text{target} = \text{toneCurve}(\text{base}) + (t - \text{base})$.
* Vì `base = 0.60` thấp hơn thực tế, khi nén Highlight ($-Highlight$), hàm `toneCurve(0.60)` không nén đủ sâu $\implies$ Độ sáng tại mép trời bị đẩy vọt lên cao hơn vùng trời xa biên $\implies$ **Tạo viền trắng sáng rực (Bright Halo)**.
* Ngược lại, phía ngọn núi tối ($t \approx 0.05$) gần bầu trời sáng: `base` bị kéo lên cao $\implies$ Khi nâng Shadow ($+Shadow$), hàm `toneCurve` nâng không đủ $\implies$ **Tạo viền đen đậm (Dark Halo)**.
### B. Tại Sao Ảnh RAW Bị Ảnh Hưởng Nghiêm Trọng Hơn Ảnh JPEG?
* Ảnh JPEG 8-bit đã qua nén tương phản và kẹp dải sáng ($0.0 - 1.0$), chênh lệch bước nhảy $t - \text{base}$ ở mức vừa phải ($0.05 - 0.15$).
* Ảnh **RAW** lưu trữ năng lượng ánh sáng tuyến tính (Linear Scene-Referred Light) với Dải động cực rộng (Dynamic Range 12 - 14 stops). Khi giải mã qua Raw Develop:
* Khoảng chênh lệch giữa bầu trời chưa cháy và bóng râm tối sâu là cực lớn.
* Bước nhảy $|t - \text{base}|$ tại đường biên tương phản đạt tới $0.30 - 0.75$.
* Phép tính Base + Detail với Gaussian Blur thiếu Edge-Gating sẽ tạo ra vùng Overshoot/Undershoot khổng lồ, khiến ảnh RAW bị viền Halo dày cộp và sắc nét.
### C. Tác Động Dây Chuyền Đến Color (Saturation) Và Vibrance
1. **Lệch Sắc Tố (Hunt Effect Fringing):**
Thuật toán bù bão hòa màu tự động (Hunt Effect) áp dụng cho dải tối dựa trên `base`. Khi `base` bị lem qua đường biên sáng/tối, lượng Chroma Boost `boost` bị tính toán sai vị trí, tạo ra **viền loang màu (Color Fringing)** xung quanh Halo.
2. **Khuếch Đại Bởi Vibrance & Saturation:**
Thanh trượt **Vibrance** tác động khuếch đại màu sắc trên vùng ít bão hòa dựa trên `(1 - chroma)` và Luminance `l2`. Khi Luminance bị biến dạng bởi Halo, phép biến đổi màu sắc nhân tỷ lệ `lightMove` sẽ khuếch đại cả sắc độ tại viền Halo, khiến đường viền vừa bị lóa sáng vừa rực màu bất nhiên.
---
## 3. Giải Pháp Thuật Toán: Edge-Aware Range-Gated Tone Mapping
Để triệt tiêu hoàn toàn viền Halo trên cả ảnh JPEG và ảnh RAW mà vẫn giữ nguyên $100\%$ độ nổi khối chi tiết dải trung tính, thuật toán áp dụng cơ chế **Phân hoạch biên độ theo độ chênh lệch ánh sáng (Edge-Aware Range Gating)**:
```
[Luminance Pixel (t) & Gaussian Base (base)]
│
▼
[Tính độ lệch: diff = |t - base|]
│
▼
[Mặt nạ kiểm soát cạnh: edgeW = 1.0 - smoothstep(0.08, 0.35, diff)]
│
┌─────────────────┴─────────────────┐
▼ (diff nhỏ < 0.08) ▼ (diff lớn > 0.35 - Bước nhảy cạnh cứng)
[edgeW ≈ 1.0: Môi trường bề mặt] [edgeW ≈ 0.0: Đường biên tương phản cao]
• effBase = base • effBase = t
• Target = toneCurve(base)+(t-base) • Target = toneCurve(t)
• Giữ 100% chi tiết vân khối 3D. • Chuyển mượt về đường cong toàn cục.
• TRIỆT TIÊU HOÀN TOÀN HALO!
```
### Công Thức Toán Học Cho WebGL / SkSL Shader:
```glsl
// 1. Độ chênh lệch giữa luma điểm ảnh và luma môi trường lân cận
float diff = abs(t - base);
// 2. Mặt nạ suy giảm mượt (Range Falloff Window)
// Với diff < 0.08: edgeW = 1.0 (vùng vân mịn / dải chuyển mượt)
// Với diff > 0.35: edgeW = 0.0 (đường biên tương phản cứng, đặc biệt trên ảnh RAW)
float edgeW = 1.0 - smoothstep(0.08, 0.35, diff);
// 3. Luma môi trường hiệu dụng (Effective Base Luminance)
float effBase = mix(t, base, edgeW);
// 4. Áp dụng toneCurve trên effBase
float o = toneCurve(effBase, clamp(bl + dr * 0.12, -1.0, 1.0),
clamp(sh + dr * 0.06, -1.0, 1.0),
clamp(hl - dr * 0.09, -1.0, 1.0),
clamp(wh - dr * 0.18, -1.0, 1.0));
// 5. Tái cấu trúc luma đích không bị vệt halo
float target = clamp(o + (t - effBase), 0.0, 1.0);
vec3 res = lightMove(c, t, target);
// 6. Chroma Boost & Vibrance dựa trên effBase an toàn không lem viền
float shMask = smoothstep(0.02, 0.12, effBase) * (1.0 - smoothstep(0.25, 0.55, effBase));
float uBl = clamp(1.0 - effBase / TONE_BLACK_EDGE, 0.0, 1.0);
```
---
## 4. Cân Chỉnh Sự Tương Tác Với Color (Saturation) & Vibrance
1. **Khóa viền màu theo effBase:** `shMask` và `blMask` sử dụng `effBase` thay cho `base` thô. Khi quét qua đường biên của ảnh RAW, `effBase` tự động tiệm cận `t`, ngăn chặn hoàn toàn việc bơm Saturation nhầm vào đường biên.
2. **Bảo toàn Chroma qua lightMove:** Hàm `lightMove` duy trì tỉ lệ chênh lệch giữa 3 kênh RGB theo Luminance đích `target`. Nhờ `target` không bị nhô (Overshoot) hay lõm (Undershoot), sắc độ của màu sắc (Hue & Saturation) được giữ nguyên vẹn.
3. **Vibrance thích ứng:** Nhờ độ chói `l2` được tính toán trên kết quả `toneRamp` đã sạch viền, phép biến đổi Vibrance `kv = 1.0 + vib * 0.75 * (1.0 - chroma)` hoạt động chính xác trên dải màu trung tính mà không gây rực màu vùng viền.
---
## 5. Mã Nguồn Đã Cập Nhật Trong Codebase
1. **Shader Core (`frontend/shared/utils/toneShader.ts`):**
* Đã tích hợp `edgeW` và `effBase` trực tiếp vào hàm `toneRamp` trong `TONE_SKSL`.
2. **Bộ Test Tự Động (`frontend/scripts/`):**
* `highlight-knee-check.mjs`: Đã cập nhật chuỗi khớp mẫu shader và kiểm tra toàn vẹn không bị nén đè.
* `tone-base-check.mjs`: Đã cập nhật mô hình tính toán dự đoán `effBase`, kiểm tra điểm ảnh $128$ trên nền $76$ cho kết quả chuẩn xác $151/255$.
* Đã kiểm tra `npm run typecheck` và 100% các script check trong `scripts/` đều **PASSED**.
---
## 6. Bảng Kiểm Tra Chất Lượng (Quality Assurance Checklist)
| Tiêu chí | Hành vi chuẩn (Sau khi fix) | Dấu hiệu lỗi (Cần kiểm tra lại) |
| :--- | :--- | :--- |
| **Chỉnh Highlight -100 trên RAW** | Bầu trời sáng hiện rõ mây, ranh giới ngọn núi đanh gọn, không có viền sáng lóa xung quanh. | Xuất hiện dải viền trắng phát sáng chạy dọc mép núi. |
| **Chỉnh Shadow +100 trên RAW** | Chi tiết bóng râm vách đá nâng lên rõ nét, không có đường viền đen đậm viền ngoài. | Có viền đen/tối sẫm bao quanh các tán cây ngược sáng. |
| **Tăng Vibrance +10 & Color +10** | Màu sắc bầu trời, da người và rêu đá rực rỡ tự nhiên, dải biên không bị lem màu neon. | Đường viền núi bị nổi màu xanh/đỏ rực rỡ như nét tô viền (Color Fringing). |