Create debug recipe photo overlay
This commit is contained in:
parent
726b24f07b
commit
381dd43263
@ -1,24 +1,22 @@
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { getPhotos } from '@/photo/db/query';
|
||||
import PhotoRecipe from '@/photo/PhotoRecipe';
|
||||
import clsx from 'clsx/lite';
|
||||
import PhotoRecipeOverlay from '@/photo/PhotoRecipeOverlay';
|
||||
|
||||
export default async function AdminRecipePage() {
|
||||
const photos = await getPhotos({ hidden: 'only' });
|
||||
const { fujifilmRecipe, filmSimulation } = photos[0];
|
||||
const photos = await getPhotos({ limit: 1});
|
||||
const photosHidden = await getPhotos({ hidden: 'only' });
|
||||
const { fujifilmRecipe, filmSimulation } = photosHidden[0];
|
||||
return (
|
||||
<SiteGrid
|
||||
contentMain={<div className={clsx(
|
||||
'w-full min-h-[min(500px,70vh)]',
|
||||
'flex items-center justify-center',
|
||||
)}>
|
||||
{(fujifilmRecipe && filmSimulation) &&
|
||||
<PhotoRecipe
|
||||
recipe={fujifilmRecipe}
|
||||
simulation={filmSimulation}
|
||||
/>
|
||||
}
|
||||
</div>}
|
||||
contentMain={photos[0] && fujifilmRecipe && filmSimulation
|
||||
? <PhotoRecipeOverlay
|
||||
backgroundImageUrl={photos[0].url}
|
||||
recipe={fujifilmRecipe}
|
||||
simulation={filmSimulation}
|
||||
/>
|
||||
: <div>
|
||||
Can't find photo/recipe
|
||||
</div>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
132
src/photo/PhotoRecipeFrost.tsx
Normal file
132
src/photo/PhotoRecipeFrost.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
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 PhotoRecipe({
|
||||
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>;
|
||||
}
|
||||
57
src/photo/PhotoRecipeOverlay.tsx
Normal file
57
src/photo/PhotoRecipeOverlay.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
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 FieldSetWithStatus from '@/components/FieldSetWithStatus';
|
||||
import { useState } from 'react';
|
||||
import PhotoRecipe from './PhotoRecipe';
|
||||
export default function PhotoRecipeOverlay({
|
||||
backgroundImageUrl,
|
||||
recipe,
|
||||
simulation,
|
||||
}: {
|
||||
backgroundImageUrl: string
|
||||
recipe: FujifilmRecipe
|
||||
simulation: FilmSimulation
|
||||
}) {
|
||||
const [isFrosted, setIsFrosted] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<FieldSetWithStatus
|
||||
id="is-frosted"
|
||||
type="checkbox"
|
||||
label="Frosted"
|
||||
value={isFrosted ? 'true' : 'false'}
|
||||
onChange={() => setIsFrosted(!isFrosted)}
|
||||
/>
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'relative w-full aspect-[3/2]',
|
||||
)}>
|
||||
<ImageLarge
|
||||
src={backgroundImageUrl}
|
||||
alt="Image Background"
|
||||
aspectRatio={3 / 2}
|
||||
/>
|
||||
<div className={clsx(
|
||||
'absolute inset-0',
|
||||
'flex items-center justify-center',
|
||||
)}>
|
||||
{isFrosted
|
||||
? <PhotoRecipeFrost
|
||||
recipe={recipe}
|
||||
simulation={simulation}
|
||||
/> : <PhotoRecipe
|
||||
recipe={recipe}
|
||||
simulation={simulation}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user