From 338426114eedbce6082a4d549dba7ce348466d83 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Fri, 21 Feb 2025 17:24:19 -0600 Subject: [PATCH] Sketch on recipe visualization --- app/admin/recipe/[photoId]/page.tsx | 29 ++++ app/admin/recipe/page.tsx | 19 +-- src/components/SiteGrid.tsx | 6 +- src/photo/PhotoRecipe.tsx | 10 +- ...cipeFrost.tsx => PhotoRecipeFrostDark.tsx} | 2 +- src/photo/PhotoRecipeFrostLight.tsx | 150 ++++++++++++++++++ src/photo/PhotoRecipeOverlay.tsx | 4 +- 7 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 app/admin/recipe/[photoId]/page.tsx rename src/photo/{PhotoRecipeFrost.tsx => PhotoRecipeFrostDark.tsx} (98%) create mode 100644 src/photo/PhotoRecipeFrostLight.tsx diff --git a/app/admin/recipe/[photoId]/page.tsx b/app/admin/recipe/[photoId]/page.tsx new file mode 100644 index 00000000..20841206 --- /dev/null +++ b/app/admin/recipe/[photoId]/page.tsx @@ -0,0 +1,29 @@ +import SiteGrid from '@/components/SiteGrid'; +import { getPhoto, getPhotos } from '@/photo/db/query'; +import PhotoRecipeOverlay from '@/photo/PhotoRecipeOverlay'; + +export default async function AdminRecipePage({ + params, +}: { + params: Promise<{ photoId: string }> +}) { + const { photoId } = await params; + const photo = await getPhoto(photoId); + const photosHidden = await getPhotos({ hidden: 'only' }); + const { filmSimulation } = photo!; + const { fujifilmRecipe } = photosHidden[0]; + + return ( + + :
+ Can't find photo/recipe +
} + /> + ); +} diff --git a/app/admin/recipe/page.tsx b/app/admin/recipe/page.tsx index cf1178a1..8401a97c 100644 --- a/app/admin/recipe/page.tsx +++ b/app/admin/recipe/page.tsx @@ -1,22 +1,7 @@ -import SiteGrid from '@/components/SiteGrid'; import { getPhotos } from '@/photo/db/query'; -import PhotoRecipeOverlay from '@/photo/PhotoRecipeOverlay'; +import { redirect } from 'next/navigation'; export default async function AdminRecipePage() { const photos = await getPhotos({ limit: 1}); - const photosHidden = await getPhotos({ hidden: 'only' }); - const { fujifilmRecipe, filmSimulation } = photosHidden[0]; - return ( - - :
- Can't find photo/recipe -
} - /> - ); + redirect(`/admin/recipe/${photos[0].id}`); } diff --git a/src/components/SiteGrid.tsx b/src/components/SiteGrid.tsx index 59bbd0a1..842caccd 100644 --- a/src/components/SiteGrid.tsx +++ b/src/components/SiteGrid.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx/lite'; -import { JSX, RefObject } from 'react'; +import { HTMLAttributes, JSX, RefObject } from 'react'; /* MAX WIDTHS @@ -18,6 +18,7 @@ export default function SiteGrid({ contentSide, sideFirstOnMobile, sideHiddenOnMobile, + ...props }: { containerRef?: RefObject className?: string @@ -25,9 +26,10 @@ export default function SiteGrid({ contentSide?: JSX.Element sideFirstOnMobile?: boolean sideHiddenOnMobile?: boolean -}) { +} & HTMLAttributes) { return (
-
+
DR @@ -66,7 +66,7 @@ export default function PhotoRecipe({
{whiteBalanceFormatted.length <= 8 && 'AWB: '} @@ -80,17 +80,17 @@ export default function PhotoRecipe({ {')'} }
-
+
{renderDataSquare('Highlight', highlight)} {renderDataSquare('Shadow', shadow)}
-
+
{/* TODO: Confirm color vs saturation label */} {renderDataSquare('Color', color)} {renderDataSquare('Sharp', sharpness)} {renderDataSquare('Clarity', clarity)}
-
+
{renderDataSquare('Chrome', colorChromeEffect)} {renderDataSquare('FX Blue', colorChromeFXBlue)}
diff --git a/src/photo/PhotoRecipeFrost.tsx b/src/photo/PhotoRecipeFrostDark.tsx similarity index 98% rename from src/photo/PhotoRecipeFrost.tsx rename to src/photo/PhotoRecipeFrostDark.tsx index de059b09..d0b9bb20 100644 --- a/src/photo/PhotoRecipeFrost.tsx +++ b/src/photo/PhotoRecipeFrostDark.tsx @@ -5,7 +5,7 @@ import clsx from 'clsx/lite'; const addSign = (value = 0) => value < 0 ? value : `+${value}`; -export default function PhotoRecipe({ +export default function PhotoRecipeFrostDark({ recipe: { dynamicRange, whiteBalance, diff --git a/src/photo/PhotoRecipeFrostLight.tsx b/src/photo/PhotoRecipeFrostLight.tsx new file mode 100644 index 00000000..d65e5d88 --- /dev/null +++ b/src/photo/PhotoRecipeFrostLight.tsx @@ -0,0 +1,150 @@ +import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; +import { FilmSimulation } from '@/simulation'; +import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; +import clsx from 'clsx/lite'; + +const addSign = (value = 0) => value < 0 ? value : `+${value}`; + +const getRandomInt = () => { + const randomInt = Math.floor(Math.random() * 4) + 1; + return Math.random() >= 0.5 ? randomInt : -randomInt; +}; + +const random = { + highlight: getRandomInt(), + shadow: getRandomInt(), + color: getRandomInt(), + sharpness: getRandomInt(), + clarity: getRandomInt(), + colorChromeEffect: getRandomInt(), + colorChromeFXBlue: getRandomInt(), +}; + +export default function PhotoRecipeFrostLight({ + recipe: { + dynamicRange, + whiteBalance, + highISONoiseReduction, + noiseReductionBasic, + highlight, + shadow, + color, + sharpness, + clarity, + colorChromeEffect, + colorChromeFXBlue, + grainEffect, + bwAdjustment, + bwMagentaGreen, + }, + simulation, +}: { + recipe: FujifilmRecipe + simulation: FilmSimulation +}) { + const whiteBalanceFormatted = (whiteBalance?.type ?? 'auto') + .replaceAll('auto', ' ') + .replaceAll('-', ' '); + + const hasCustomizedWhiteBalance = + Boolean(whiteBalance?.red) || + Boolean(whiteBalance?.blue); + + const hasBWAdjustments = + Boolean(bwAdjustment) || + Boolean(bwMagentaGreen); + + const renderDataSquare = (label: string, value: string | number = '0') => ( +
+
{typeof value === 'number' ? addSign(value) : value}
+
+ {label} +
+
+ ); + + return
+
+
+ +
+ DR + {dynamicRange ?? 100} +
+
+
+
+ {whiteBalanceFormatted.length <= 8 && 'AWB: '} + {whiteBalanceFormatted} + {hasCustomizedWhiteBalance && <> + {' '} + {'('} + R {addSign(whiteBalance?.red ?? 0)} + / + B {addSign(whiteBalance?.blue ?? 0)} + {')'} + } +
+
+ {renderDataSquare('Highlight', highlight || random.highlight)} + {renderDataSquare('Shadow', shadow || random.shadow)} +
+
+ {/* TODO: Confirm color vs saturation label */} + {renderDataSquare('Color', color || random.color)} + {renderDataSquare('Sharpness', sharpness || random.sharpness)} + {renderDataSquare('Clarity', clarity || random.clarity)} +
+
+ {renderDataSquare('Color Chrome', colorChromeEffect)} + {renderDataSquare('FX Blue', colorChromeFXBlue)} +
+
+ {highISONoiseReduction !== undefined + ? <> + High ISO NR: + {addSign(highISONoiseReduction)} + + : <> + Noise Reduction: + {noiseReductionBasic} + + } +
+ {grainEffect && +
+ Grain: + {grainEffect.roughness} + {' / '} + {grainEffect.size} +
} + {hasBWAdjustments && +
+ BW Adjustment: + {addSign(bwAdjustment)} + {' / '} + MG: {addSign(bwMagentaGreen)} +
} +
+
+
; +} diff --git a/src/photo/PhotoRecipeOverlay.tsx b/src/photo/PhotoRecipeOverlay.tsx index 281f6d2b..7cdca292 100644 --- a/src/photo/PhotoRecipeOverlay.tsx +++ b/src/photo/PhotoRecipeOverlay.tsx @@ -4,7 +4,7 @@ import { FujifilmRecipe } from '@/platforms/fujifilm/recipe'; import { FilmSimulation } from '@/simulation'; import clsx from 'clsx/lite'; import ImageLarge from '@/components/image/ImageLarge'; -import PhotoRecipeFrost from './PhotoRecipeFrost'; +import PhotoRecipeFrostLight from './PhotoRecipeFrostLight'; import FieldSetWithStatus from '@/components/FieldSetWithStatus'; import { useState } from 'react'; import PhotoRecipe from './PhotoRecipe'; @@ -43,7 +43,7 @@ export default function PhotoRecipeOverlay({ 'flex items-center justify-center', )}> {isFrosted - ? :