Files
RecipesCam/PLAN-WEB-TO-ANDROID.md
T
3dtours e0526283ca docs: note the two web looks that still have to reach Android
The web studio grew a CLASSIC VIVIDIPES sim (Classic Chrome's blue row spliced
to Velvia's red and green) and an FX MONOCHROME switch; both are written up
here with the exact matrices, anchors and the checks they have to pass before
they count as ported.
2026-09-18 16:17:07 +07:00

5.7 KiB

Ghi chú port web → Android: sim CLASSIC VIVIDIPES + chip FX MONOCHROME

Nguồn: web studio docker/frontend (nhánh recipes-web), commit d7d7be2 (sim mới) và 4d0d441 (chip FX). Việc dưới đây là port sang app Android (src/, nhánh này).

Đối chiếu: src/ giữa recipes-web và feat/vision-camera-v5 chỉ khác src/components/RecipeCreateModal.tsx (+9 dòng) — nên port gần như là chép y nguyên. Số dòng dưới đây tính theo nhánh này.


1. Sim mới CLASSIC VIVIDIPES

Ghép tại dòng blue: giữ nguyên dòng blue của classic-chrome, lấy dòng R/G của velvia.

Sửa 4 file:

  1. src/types/index.ts:65 — BaseFilter: thêm 'classic-vivid'.
  2. src/utils/colorUtils.ts — thêm nhánh ngay sau nhánh classic-chrome (dòng 111):
    } else if (baseFilter === 'classic-vivid') {
      // Classic Vivid: the Classic Chrome blue row kept verbatim — skies stay the
      // same muted teal/cyan — with the red and green rows taken from Velvia, so
      // everything that is not blue reads loud.
      matrix = [
        1.3895, -0.2540, -0.0256, 0, -0.0550,   // R — velvia
        -0.0772, 1.2355, -0.0261, 0, -0.0661,   // G — velvia
        0.0652, 0.3973, 0.5575, 0, -0.0100,     // B — classic-chrome (nguyên bản)
        0, 0, 0, 1, 0,
      ];
    
  3. src/utils/defaultRecipes.ts:43 — FILM_SIMS, thêm ngay sau sim-classic-chrome:
    { id: 'sim-classic-vivid', name: 'CLASSIC VIVIDIPES', baseFilter: 'classic-vivid', adjustments: { ...DEFAULT_ADJUSTMENTS } },
    
  4. src/utils/toneShader.ts:190 — FILM_TONE, cạnh 'classic-chrome':
    'classic-vivid': { sh: -0.28 },
    
    (Dìm shadow thuộc về stock chứ không thuộc dòng ma trận → đi kèm. Bỏ dòng này nếu muốn VIVIDIPES không dìm shadow.)

Không phải sửa gì thêm: chip PHOTO STYLE (AdjustmentPanel.tsx groupDefs.style), TopBar (activeRecipe.name) và picker trong RecipeCreateModal.tsx đều map từ FILM_SIMS.

Kiểm chứng: strip PHOTO STYLE có 10 sim; chọn CLASSIC VIVIDIPES → look khác cả CLASSIC CHRIPES lẫn VELVIPES; CLASSIC CHRIPES vẫn nguyên vẹn cạnh nó.


2. Chip MONOCHROME ở tab FX (filters)

Đây là công tắc, không phải một look: bật thì đổi baseFilter sang monochrome, tắt thì trả lại đúng cái ảnh đang mặc (kể cả tên sim). Không được đụng adjustments — knob chỉnh trong lúc đang bật phải sống sót.

Web: docker/frontend/src/App.tsx — monoPrev ref + toggleMono, chip mono đứng đầu case 'fx', active: monoOn với monoOn = recipe.baseFilter === 'monochrome'.

Android cần 2 chỗ:

a. src/components/AdjustmentPanel.tsx — case 'filters' (dòng 888):

case 'filters':
  return chipRow([
    { key: 'mono', label: 'MONOCHROME', active: monoOn, amberValue: monoOn, onPress: onToggleMono },
    ...paramChips(paramDefs.filters),
  ]);
  • thêm 2 prop vào interface AdjustmentPanelProps: monoOn: boolean; và onToggleMono: () => void;
  • truyền từ App.tsx chỗ render <AdjustmentPanel … /> (App.tsx:1929 có currentBaseFilter, onSelectRecipe — đặt cạnh đó).

b. App.tsx — Android giữ adjustments là state riêng (không nằm trong selectedRecipe), nên công tắc chỉ chạm selectedRecipe:

// FX's MONOCHROME switch: not a look of its own — it swaps the base filter for
// the mono stock and swaps right back. Adjustments are never touched.
const monoOn = selectedRecipe?.baseFilter === 'monochrome';
const monoPrevRef = useRef<Recipe | null>(null);

const handleToggleMono = () => {
  commit(true, () => {
    if (selectedRecipe?.baseFilter === 'monochrome') {
      const back = monoPrevRef.current ?? filmSimToRecipe(FILM_SIMS.find((f) => f.id === 'sim-provia')!);
      setSelectedRecipe(back);
    } else {
      monoPrevRef.current = selectedRecipe;
      setSelectedRecipe({
        ...(selectedRecipe ?? filmSimToRecipe(FILM_SIMS[0])),
        id: 'mono',
        name: 'MONOCHROME',
        baseFilter: 'monochrome',
      });
    }
  });
};

Lưu ý:

  • commit(true, …) (dòng 739) để công tắc vào undo stack như mọi edit khác — handleResetAll dùng đúng cách này.
  • Look đã có recipe → UNDO tự trả lại đúng baseFilter cũ, không cần state phụ nào khác.
  • id: 'mono' không trùng sim nào nên khi bật, chip PHOTO STYLE không còn sáng tên sim cũ (groupDefs.style.value tính từ currentRecipeId so với FILM_SIMS).
  • Tắt khi monoPrevRef rỗng (ví dụ vào monochrome bằng cách chọn sim ACRIPES, không qua công tắc) → rơi về PROVIA, tức là "tắt là có màu", không phải về đen trắng.

Kiểm chứng: bật → ảnh xám (mọi pixel R=G=B; chỉ lệch khi WB khác 5500K hoặc tint khác 0); tắt → ảnh trở lại y hệt trước khi bật (so hash pixel); đổi CONTRAST trong lúc bật/tắt → giá trị vẫn giữ; UNDO một nhịp → về màu, chip trở lại trạng thái tắt.


3. Test web đã chạy (tham chiếu — nằm trong scratchpad của agent, không nằm trong repo)

  • sim-vivid-test.cjs — 9/0: chip có mặt, strip đủ 10 sim, chọn được, render khác cả CHRIPES lẫn VELVIPES.
  • fx-mono-test.cjs — 15/0: đo trực tiếp kênh màu trên pixel preview (spread >25 khi màu, <=6 khi mono), hash quay lại y hệt khi tắt, CONTRAST sống sót, UNDO gỡ mono.
  • Regression web quanh 2 thay đổi: panel, layout, fonts 27/0, create 52/0, share 22/0, photo-save 19/0, redo 30/0, accent-brand — tất cả xanh.