Standardize on one recipe layout

This commit is contained in:
Sam Becker 2025-02-22 11:37:34 -06:00
parent d404aeede3
commit dbb743468d
5 changed files with 89 additions and 503 deletions

View File

@ -2,9 +2,25 @@ import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FilmSimulation } from '@/simulation'; import { FilmSimulation } from '@/simulation';
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
import clsx from 'clsx/lite'; import clsx from 'clsx/lite';
import { IoCloseCircle } from 'react-icons/io5';
const addSign = (value = 0) => value < 0 ? value : `+${value}`; 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 PhotoRecipe({ export default function PhotoRecipe({
recipe: { recipe: {
dynamicRange, dynamicRange,
@ -23,29 +39,31 @@ export default function PhotoRecipe({
bwMagentaGreen, bwMagentaGreen,
}, },
simulation, simulation,
exposure,
iso,
}: { }: {
recipe: FujifilmRecipe recipe: FujifilmRecipe
simulation: FilmSimulation simulation: FilmSimulation
exposure: string
iso: string
}) { }) {
const whiteBalanceFormatted = (whiteBalance?.type ?? 'auto') const whiteBalanceFormatted = (whiteBalance?.type ?? 'auto')
.replaceAll('auto', ' ') .replaceAll('auto', ' ')
.replaceAll('-', ' '); .replaceAll('-', ' ');
const hasCustomizedWhiteBalance =
Boolean(whiteBalance?.red) ||
Boolean(whiteBalance?.blue);
const hasBWAdjustments = const hasBWAdjustments =
Boolean(bwAdjustment) || Boolean(bwAdjustment) ||
Boolean(bwMagentaGreen); Boolean(bwMagentaGreen);
const renderDataSquare = (label: string, value: string | number = '0') => ( const renderDataSquare = (label: string, value: string | number = '0') => (
<div className={clsx( <div className={clsx(
'flex flex-col items-center justify-center', 'flex flex-col items-center justify-center gap-0.5',
'bg-dim border-medium rounded-md p-0.5', 'bg-white/25 border border-white/20 rounded-md p-1',
)}> )}>
<div>{typeof value === 'number' ? addSign(value) : value}</div> <div>{typeof value === 'number' ? addSign(value) : value}</div>
<div className="text-xs tracking-wide text-dim"> <div className={clsx(
'text-[10px] leading-none tracking-wide font-medium text-black/50',
)}>
{label} {label}
</div> </div>
</div> </div>
@ -53,72 +71,84 @@ export default function PhotoRecipe({
return <div className="flex gap-8"> return <div className="flex gap-8">
<div className={clsx( <div className={clsx(
'w-[20rem] self-start', 'w-[17rem] self-start',
'p-3', 'p-3',
'component-surface shadow-xs', 'rounded-lg shadow-2xl',
'bg-white/60 backdrop-blur-xl border border-white/30',
'space-y-3', 'space-y-3',
'text-[13px] text-black',
'saturate-200',
)}> )}>
<div className="flex items-center gap-1"> <div className="flex items-center gap-2">
<PhotoFilmSimulation {...{ simulation, className: 'grow' }} /> <PhotoFilmSimulation
<div className="bg-dim border-medium rounded-md px-1"> contrast="frost"
<span>DR</span> className="grow"
<span>{dynamicRange ?? 100}</span> simulation={simulation}
</div> />
<IoCloseCircle
size={20}
className="text-black/25"
/>
</div> </div>
<div <div className="uppercase space-y-2">
className="uppercase space-y-2" <div className="flex gap-2 *:grow">
> <div className={clsx(
<div> 'inline-flex justify-center',
{whiteBalanceFormatted.length <= 8 && 'AWB: '} 'bg-white/25 border border-white/20 rounded-md px-1',
{whiteBalanceFormatted} )}>
{hasCustomizedWhiteBalance && <> DR{dynamicRange ?? 100}
{' '} </div>
<span className="text-extra-dim">{'('}</span> <div className={clsx(
R {addSign(whiteBalance?.red ?? 0)} 'inline-flex justify-center',
<span className="text-extra-dim">/</span> 'bg-white/25 border border-white/20 rounded-md px-1',
B {addSign(whiteBalance?.blue ?? 0)} )}>
<span className="text-extra-dim">{')'}</span> {iso}
</>} </div>
<div className={clsx(
'inline-flex justify-center',
'bg-white/25 border border-white/20 rounded-md px-1',
)}>
{exposure}
</div>
</div> </div>
<div className="flex gap-2 *:w-full"> <div className="flex gap-2 *:w-full">
{renderDataSquare('Highlight', highlight)} {renderDataSquare(
{renderDataSquare('Shadow', shadow)} `R${addSign(whiteBalance?.red)} / B${addSign(whiteBalance?.blue)}`,
whiteBalanceFormatted,
)}
</div>
<div className="flex gap-2 *:w-full">
{renderDataSquare('Highlight', highlight || random.highlight)}
{renderDataSquare('Shadow', shadow || random.shadow)}
</div> </div>
<div className="flex gap-2 *:w-full"> <div className="flex gap-2 *:w-full">
{/* TODO: Confirm color vs saturation label */} {/* TODO: Confirm color vs saturation label */}
{renderDataSquare('Color', color)} {renderDataSquare('Color', color || random.color)}
{renderDataSquare('Sharp', sharpness)} {renderDataSquare('Sharpness', sharpness || random.sharpness)}
{renderDataSquare('Clarity', clarity)} {renderDataSquare('Clarity', clarity || random.clarity)}
</div> </div>
<div className="flex gap-2 *:w-full"> <div className="flex gap-2 *:w-full">
{renderDataSquare('Chrome', colorChromeEffect)} {renderDataSquare('Color Chrome', colorChromeEffect)}
{renderDataSquare('FX Blue', colorChromeFXBlue)} {renderDataSquare('FX Blue', colorChromeFXBlue)}
</div> </div>
<div>
{highISONoiseReduction !== undefined
? <>
<span>High ISO NR: </span>
<span>{addSign(highISONoiseReduction)}</span>
</>
: <>
<span>Noise Reduction: </span>
<span>{noiseReductionBasic}</span>
</>
}
</div>
{grainEffect && {grainEffect &&
<div> <div className="flex gap-2 *:w-full">
Grain: {renderDataSquare(
{' '} highISONoiseReduction !== undefined
{grainEffect.roughness} ? 'High ISO NR'
<span className="text-extra-dim">{' / '}</span> : 'Noise Reduction',
{grainEffect.size} highISONoiseReduction ?? noiseReductionBasic,
)}
{renderDataSquare(
'Grain',
// eslint-disable-next-line max-len
`${grainEffect.roughness} / ${grainEffect.size === 'large' ? 'LG' : grainEffect.size === 'small' ? 'SM' : 'OFF'}`,
)}
</div>} </div>}
{hasBWAdjustments && {hasBWAdjustments &&
<div> <div className="flex gap-2 *:w-full">
BW Adjustment: {addSign(bwAdjustment)} {renderDataSquare('BW Adjustment', bwAdjustment)}
<span className="text-extra-dim">{' / '}</span> {renderDataSquare('BW Magenta Green', bwMagentaGreen)}
MG: {addSign(bwMagentaGreen)}
</div>} </div>}
</div> </div>
</div> </div>

View File

@ -1,132 +0,0 @@
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}`;
export default function PhotoRecipeFrostDark({
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') => (
<div className={clsx(
'flex flex-col items-center justify-center',
'bg-black/30 border border-white/20 rounded-md p-1',
)}>
<div>{typeof value === 'number' ? addSign(value) : value}</div>
<div className="text-xs tracking-wide text-white/40">
{label}
</div>
</div>
);
return <div className="flex gap-8">
<div className={clsx(
'w-[20rem] self-start',
'p-3',
'rounded-lg shadow-2xl',
'bg-black/10 backdrop-blur-xl border border-white/20',
'space-y-3',
'text-main',
)}>
<div className="flex items-center gap-2">
<PhotoFilmSimulation
contrast="high"
className="grow"
simulation={simulation}
/>
<div className="bg-black/15 border border-white/20 rounded-md px-1">
<span>DR</span>
<span>{dynamicRange ?? 100}</span>
</div>
</div>
<div
className="uppercase space-y-3"
>
<div>
{whiteBalanceFormatted.length <= 8 && 'AWB: '}
{whiteBalanceFormatted}
{hasCustomizedWhiteBalance && <>
{' '}
<span className="text-extra-dim">{'('}</span>
R {addSign(whiteBalance?.red ?? 0)}
<span className="text-extra-dim">/</span>
B {addSign(whiteBalance?.blue ?? 0)}
<span className="text-extra-dim">{')'}</span>
</>}
</div>
<div className="flex gap-3 *:w-full">
{renderDataSquare('Highlight', highlight)}
{renderDataSquare('Shadow', shadow)}
</div>
<div className="flex gap-3 *:w-full">
{/* TODO: Confirm color vs saturation label */}
{renderDataSquare('Color', color)}
{renderDataSquare('Sharp', sharpness)}
{renderDataSquare('Clarity', clarity)}
</div>
<div className="flex gap-3 *:w-full">
{renderDataSquare('Chrome', colorChromeEffect)}
{renderDataSquare('FX Blue', colorChromeFXBlue)}
</div>
<div>
{highISONoiseReduction !== undefined
? <>
<span>High ISO NR: </span>
<span>{addSign(highISONoiseReduction)}</span>
</>
: <>
<span>Noise Reduction: </span>
<span>{noiseReductionBasic}</span>
</>
}
</div>
{grainEffect &&
<div>
Grain:
{' '}
{grainEffect.roughness}
<span className="text-extra-dim">{' / '}</span>
{grainEffect.size}
</div>}
{hasBWAdjustments &&
<div>
BW Adjustment: {addSign(bwAdjustment)}
<span className="text-extra-dim">{' / '}</span>
MG: {addSign(bwMagentaGreen)}
</div>}
</div>
</div>
</div>;
}

View File

@ -1,156 +0,0 @@
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FilmSimulation } from '@/simulation';
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
import clsx from 'clsx/lite';
import { IoCloseCircle } from 'react-icons/io5';
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,
exposure,
iso,
}: {
recipe: FujifilmRecipe
simulation: FilmSimulation
exposure: string
iso: string
}) {
const whiteBalanceFormatted = (whiteBalance?.type ?? 'auto')
.replaceAll('auto', ' ')
.replaceAll('-', ' ');
const hasBWAdjustments =
Boolean(bwAdjustment) ||
Boolean(bwMagentaGreen);
const renderDataSquare = (label: string, value: string | number = '0') => (
<div className={clsx(
'flex flex-col items-center justify-center gap-0.5',
'bg-white/25 border border-white/20 rounded-md p-1',
)}>
<div>{typeof value === 'number' ? addSign(value) : value}</div>
<div className={clsx(
'text-[10px] leading-none tracking-wide font-medium text-black/50',
)}>
{label}
</div>
</div>
);
return <div className="flex gap-8">
<div className={clsx(
'w-[17rem] self-start',
'p-3',
'rounded-lg shadow-2xl',
'bg-white/60 backdrop-blur-xl border border-white/30',
'space-y-3',
'text-[13px] text-black',
'saturate-200',
)}>
<div className="flex items-center gap-2">
<PhotoFilmSimulation
contrast="frost"
className="grow"
simulation={simulation}
/>
<IoCloseCircle
size={20}
className="text-black/25"
/>
</div>
<div className="uppercase space-y-2">
<div className="flex gap-2 *:grow">
<div className={clsx(
'inline-flex justify-center',
'bg-white/25 border border-white/20 rounded-md px-1',
)}>
DR{dynamicRange ?? 100}
</div>
<div className={clsx(
'inline-flex justify-center',
'bg-white/25 border border-white/20 rounded-md px-1',
)}>
{iso}
</div>
<div className={clsx(
'inline-flex justify-center',
'bg-white/25 border border-white/20 rounded-md px-1',
)}>
{exposure}
</div>
</div>
<div className="flex gap-2 *:w-full">
{renderDataSquare(
`R${addSign(whiteBalance?.red)} / B${addSign(whiteBalance?.blue)}`,
whiteBalanceFormatted,
)}
</div>
<div className="flex gap-2 *:w-full">
{renderDataSquare('Highlight', highlight || random.highlight)}
{renderDataSquare('Shadow', shadow || random.shadow)}
</div>
<div className="flex gap-2 *:w-full">
{/* TODO: Confirm color vs saturation label */}
{renderDataSquare('Color', color || random.color)}
{renderDataSquare('Sharpness', sharpness || random.sharpness)}
{renderDataSquare('Clarity', clarity || random.clarity)}
</div>
<div className="flex gap-2 *:w-full">
{renderDataSquare('Color Chrome', colorChromeEffect)}
{renderDataSquare('FX Blue', colorChromeFXBlue)}
</div>
{grainEffect &&
<div className="flex gap-2 *:w-full">
{renderDataSquare(
highISONoiseReduction !== undefined
? 'High ISO NR'
: 'Noise Reduction',
highISONoiseReduction ?? noiseReductionBasic,
)}
{renderDataSquare(
'Grain',
// eslint-disable-next-line max-len
`${grainEffect.roughness} / ${grainEffect.size === 'large' ? 'LG' : grainEffect.size === 'small' ? 'SM' : 'OFF'}`,
)}
</div>}
{hasBWAdjustments &&
<div className="flex gap-2 *:w-full">
{renderDataSquare('BW Adjustment', bwAdjustment)}
{renderDataSquare('BW Magenta Green', bwMagentaGreen)}
</div>}
</div>
</div>
</div>;
}

View File

@ -1,156 +0,0 @@
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FilmSimulation } from '@/simulation';
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
import clsx from 'clsx/lite';
import { IoCloseCircle } from 'react-icons/io5';
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 PhotoRecipeFrostLightV2({
recipe: {
dynamicRange,
whiteBalance,
highISONoiseReduction,
noiseReductionBasic,
highlight,
shadow,
color,
sharpness,
clarity,
colorChromeEffect,
colorChromeFXBlue,
grainEffect,
bwAdjustment,
bwMagentaGreen,
},
simulation,
exposure,
iso,
}: {
recipe: FujifilmRecipe
simulation: FilmSimulation
exposure: string
iso: string
}) {
const whiteBalanceFormatted = (whiteBalance?.type ?? 'auto')
.replaceAll('auto', ' ')
.replaceAll('-', ' ');
const hasBWAdjustments =
Boolean(bwAdjustment) ||
Boolean(bwMagentaGreen);
const renderDataSquare = (label: string, value: string | number = '0') => (
<div className={clsx(
'flex flex-col items-center justify-center gap-0.5',
'bg-white/25 border border-white/20 rounded-md p-1',
)}>
<div>{typeof value === 'number' ? addSign(value) : value}</div>
<div className={clsx(
'text-[10px] leading-none tracking-wide font-medium text-black/50',
)}>
{label}
</div>
</div>
);
return <div className="flex gap-8">
<div className={clsx(
'w-[17rem] self-start',
'p-3',
'rounded-lg shadow-2xl',
'bg-white/60 backdrop-blur-xl border border-white/30',
'space-y-3',
'text-[13px] text-black',
'saturate-200',
)}>
<div className="flex items-center gap-2">
<PhotoFilmSimulation
contrast="frost"
className="grow"
simulation={simulation}
/>
<IoCloseCircle
size={20}
className="text-black/25"
/>
</div>
<div className="uppercase space-y-2">
<div className="flex gap-2 *:grow">
<div className={clsx(
'inline-flex justify-center',
'bg-white/25 border border-white/20 rounded-md px-1',
)}>
DR{dynamicRange ?? 100}
</div>
<div className={clsx(
'inline-flex justify-center',
'bg-white/25 border border-white/20 rounded-md px-1',
)}>
{iso}
</div>
<div className={clsx(
'inline-flex justify-center',
'bg-white/25 border border-white/20 rounded-md px-1',
)}>
{exposure}
</div>
</div>
<div className="flex gap-2 *:w-full">
{renderDataSquare(
`R${addSign(whiteBalance?.red)} / B${addSign(whiteBalance?.blue)}`,
whiteBalanceFormatted,
)}
</div>
<div className="flex gap-2 *:w-full">
{renderDataSquare('Highlight', highlight || random.highlight)}
{renderDataSquare('Shadow', shadow || random.shadow)}
</div>
<div className="flex gap-2 *:w-full">
{/* TODO: Confirm color vs saturation label */}
{renderDataSquare('Color', color || random.color)}
{renderDataSquare('Sharpness', sharpness || random.sharpness)}
{renderDataSquare('Clarity', clarity || random.clarity)}
</div>
<div className="flex gap-2 *:w-full">
{renderDataSquare('Color Chrome', colorChromeEffect)}
{renderDataSquare('FX Blue', colorChromeFXBlue)}
</div>
{grainEffect &&
<div className="flex gap-2 *:w-full">
{renderDataSquare(
highISONoiseReduction !== undefined
? 'High ISO NR'
: 'Noise Reduction',
highISONoiseReduction ?? noiseReductionBasic,
)}
{renderDataSquare(
'Grain',
// eslint-disable-next-line max-len
`${grainEffect.roughness} / ${grainEffect.size === 'large' ? 'LG' : grainEffect.size === 'small' ? 'SM' : 'OFF'}`,
)}
</div>}
{hasBWAdjustments &&
<div className="flex gap-2 *:w-full">
{renderDataSquare('BW Adjustment', bwAdjustment)}
{renderDataSquare('BW Magenta Green', bwMagentaGreen)}
</div>}
</div>
</div>
</div>;
}

View File

@ -2,7 +2,7 @@ import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FilmSimulation } from '@/simulation'; import { FilmSimulation } from '@/simulation';
import clsx from 'clsx/lite'; import clsx from 'clsx/lite';
import ImageLarge from '@/components/image/ImageLarge'; import ImageLarge from '@/components/image/ImageLarge';
import PhotoRecipeFrostLightV2 from './PhotoRecipeFrostLightV2'; import PhotoRecipe from './PhotoRecipe';
export default function PhotoRecipeOverlay({ export default function PhotoRecipeOverlay({
backgroundImageUrl, backgroundImageUrl,
@ -31,7 +31,7 @@ export default function PhotoRecipeOverlay({
'absolute inset-0', 'absolute inset-0',
'flex items-center justify-center', 'flex items-center justify-center',
)}> )}>
<PhotoRecipeFrostLightV2 {...{ <PhotoRecipe {...{
recipe, recipe,
simulation, simulation,
exposure, exposure,