Display basic fujifilm recipes
This commit is contained in:
parent
a63c05a502
commit
62a681a424
@ -40,6 +40,7 @@ import { LuExpand } from 'react-icons/lu';
|
||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
import ZoomControls, { ZoomControlsRef } from '@/components/image/ZoomControls';
|
||||
import PhotoRecipe from './PhotoRecipe';
|
||||
|
||||
export default function PhotoLarge({
|
||||
photo,
|
||||
@ -275,10 +276,15 @@ export default function PhotoLarge({
|
||||
<li>{photo.exposureCompensationFormatted ?? '0ev'}</li>
|
||||
</ul>
|
||||
{showSimulation && photo.filmSimulation &&
|
||||
<PhotoFilmSimulation
|
||||
simulation={photo.filmSimulation}
|
||||
prefetch={prefetchRelatedLinks}
|
||||
/>}
|
||||
<Tooltip content={photo.fujifilmRecipe
|
||||
? <PhotoRecipe recipe={photo.fujifilmRecipe} />
|
||||
: undefined
|
||||
}>
|
||||
<PhotoFilmSimulation
|
||||
simulation={photo.filmSimulation}
|
||||
prefetch={prefetchRelatedLinks}
|
||||
/>
|
||||
</Tooltip>}
|
||||
</>}
|
||||
<div className={clsx(
|
||||
'flex gap-x-3 gap-y-baseline',
|
||||
|
||||
93
src/photo/PhotoRecipe.tsx
Normal file
93
src/photo/PhotoRecipe.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
import clsx from 'clsx/lite';
|
||||
|
||||
const addSign = (value: number) => value < 0 ? value : `+${value}`;
|
||||
|
||||
export default function PhotoRecipe({ recipe: {
|
||||
dynamicRange,
|
||||
highlight,
|
||||
shadow,
|
||||
color,
|
||||
highISONoiseReduction,
|
||||
noiseReductionLegacy,
|
||||
sharpness,
|
||||
clarity,
|
||||
grainEffect,
|
||||
colorChromeEffect,
|
||||
colorChromeFXBlue,
|
||||
whiteBalance,
|
||||
bwAdjustment,
|
||||
bwMagentaGreen,
|
||||
} }: { recipe: FujifilmRecipe }) {
|
||||
return <div className="text-left space-y-4">
|
||||
<div className="font-bold">
|
||||
Fujifilm Recipe
|
||||
</div>
|
||||
<div className={clsx(
|
||||
'grid grid-cols-2 gap-2',
|
||||
'*:odd:text-dim *:even:uppercase',
|
||||
)}>
|
||||
{dynamicRange !== undefined && <>
|
||||
<div>DR</div>
|
||||
<div>{dynamicRange}</div>
|
||||
</>}
|
||||
{highlight !== undefined && <>
|
||||
<div>Highlight</div>
|
||||
<div>{addSign(highlight)}</div>
|
||||
</>}
|
||||
{shadow !== undefined && <>
|
||||
<div>Shadow</div>
|
||||
<div>{addSign(shadow)}</div>
|
||||
</>}
|
||||
{color !== undefined && <>
|
||||
<div>Color</div>
|
||||
<div>{addSign(color)}</div>
|
||||
</>}
|
||||
{highISONoiseReduction !== undefined && <>
|
||||
<div>High ISO NR</div>
|
||||
<div>{addSign(highISONoiseReduction)}</div>
|
||||
</>}
|
||||
{noiseReductionLegacy !== undefined && <>
|
||||
<div>NR</div>
|
||||
<div>{noiseReductionLegacy}</div>
|
||||
</>}
|
||||
{sharpness !== undefined && <>
|
||||
<div>Sharpness</div>
|
||||
<div>{addSign(sharpness)}</div>
|
||||
</>}
|
||||
{clarity !== undefined && <>
|
||||
<div>Clarity</div>
|
||||
<div>{addSign(clarity)}</div>
|
||||
</>}
|
||||
{grainEffect !== undefined && <>
|
||||
<div>Grain</div>
|
||||
<div>{grainEffect.roughness} / {grainEffect.size}</div>
|
||||
</>}
|
||||
{colorChromeEffect !== undefined && <>
|
||||
<div>Chrome</div>
|
||||
<div>{colorChromeEffect}</div>
|
||||
</>}
|
||||
{colorChromeFXBlue !== undefined && <>
|
||||
<div>Chrome FX Blue</div>
|
||||
<div>{colorChromeFXBlue}</div>
|
||||
</>}
|
||||
{whiteBalance !== undefined && <>
|
||||
<div>White Balance</div>
|
||||
<div>
|
||||
<div>{whiteBalance.type}</div>
|
||||
<div>
|
||||
{addSign(whiteBalance.red)} / {addSign(whiteBalance.blue)}
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
{bwAdjustment !== undefined && <>
|
||||
<div>BW</div>
|
||||
<div>{addSign(bwAdjustment)}</div>
|
||||
</>}
|
||||
{bwMagentaGreen !== undefined && <>
|
||||
<div>BW MG</div>
|
||||
<div>{addSign(bwMagentaGreen)}</div>
|
||||
</>}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
@ -187,6 +187,8 @@ export const convertPhotoToFormData = (
|
||||
return value?.toISOString ? value.toISOString() : value;
|
||||
case 'hidden':
|
||||
return value ? 'true' : 'false';
|
||||
case 'fujifilmRecipe':
|
||||
return JSON.stringify(value);
|
||||
default:
|
||||
return value !== undefined && value !== null
|
||||
? value.toString()
|
||||
@ -206,7 +208,7 @@ export const convertPhotoToFormData = (
|
||||
export const convertExifToFormData = (
|
||||
data: ExifData,
|
||||
filmSimulation?: FilmSimulation,
|
||||
fujifilmRecipe?: Partial<FujifilmRecipe>,
|
||||
fujifilmRecipe?: FujifilmRecipe,
|
||||
): Omit<
|
||||
Record<keyof PhotoExif, string | undefined>,
|
||||
'takenAt' | 'takenAtNaive'
|
||||
|
||||
@ -91,16 +91,15 @@ export interface PhotoDbInsert extends PhotoExif {
|
||||
|
||||
// Raw db response
|
||||
export interface PhotoDb extends
|
||||
Omit<PhotoDbInsert, 'takenAt' | 'tags' | 'fujifilmRecipe'> {
|
||||
Omit<PhotoDbInsert, 'takenAt' | 'tags'> {
|
||||
updatedAt: Date
|
||||
createdAt: Date
|
||||
takenAt: Date
|
||||
tags: string[]
|
||||
fujifilmRecipe?: Partial<FujifilmRecipe>
|
||||
}
|
||||
|
||||
// Parsed db response
|
||||
export interface Photo extends PhotoDb {
|
||||
export interface Photo extends Omit<PhotoDb, 'fujifilmRecipe'> {
|
||||
focalLengthFormatted?: string
|
||||
focalLengthIn35MmFormatFormatted?: string
|
||||
fNumberFormatted?: string
|
||||
@ -108,6 +107,7 @@ export interface Photo extends PhotoDb {
|
||||
exposureTimeFormatted?: string
|
||||
exposureCompensationFormatted?: string
|
||||
takenAtNaiveFormatted: string
|
||||
fujifilmRecipe?: FujifilmRecipe
|
||||
}
|
||||
|
||||
export interface PhotoSetCategory {
|
||||
@ -143,6 +143,9 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
|
||||
formatExposureTime(photoDb.exposureTime),
|
||||
exposureCompensationFormatted:
|
||||
formatExposureCompensation(photoDb.exposureCompensation),
|
||||
fujifilmRecipe: photoDb.fujifilmRecipe
|
||||
? JSON.parse(photoDb.fujifilmRecipe)
|
||||
: undefined,
|
||||
takenAtNaiveFormatted:
|
||||
formatDateFromPostgresString(photoDb.takenAtNaive),
|
||||
};
|
||||
|
||||
@ -51,7 +51,7 @@ export const extractImageDataFromBlobPath = async (
|
||||
|
||||
let exifData: ExifData | undefined;
|
||||
let filmSimulation: FilmSimulation | undefined;
|
||||
let recipe: Partial<FujifilmRecipe> | undefined;
|
||||
let recipe: FujifilmRecipe | undefined;
|
||||
let blurData: string | undefined;
|
||||
let imageResizedBase64: string | undefined;
|
||||
let shouldStripGpsData = false;
|
||||
|
||||
@ -5,7 +5,7 @@ const TAG_ID_HIGHLIGHT = 0x1041;
|
||||
const TAG_ID_SHADOW = 0x1040;
|
||||
const TAG_ID_SATURATION = 0x1003;
|
||||
const TAG_ID_NOISE_REDUCTION = 0x100e;
|
||||
const TAG_ID_NOISE_REDUCTION_LEGACY = 0x100b;
|
||||
const TAG_ID_NOISE_REDUCTION_BASIC = 0x100b;
|
||||
const TAG_ID_SHARPNESS = 0x1001;
|
||||
const TAG_ID_CLARITY = 0x100f;
|
||||
const TAG_ID_GRAIN_EFFECT_ROUGHNESS = 0x1047;
|
||||
@ -19,7 +19,7 @@ const TAG_ID_BW_MAGENTA_GREEN = 0x104b;
|
||||
|
||||
type WeakStrong = 'off' | 'weak' | 'strong';
|
||||
|
||||
export interface FujifilmRecipe {
|
||||
export type FujifilmRecipe = Partial<{
|
||||
dynamicRange: number
|
||||
highlight: number
|
||||
shadow: number
|
||||
@ -41,7 +41,7 @@ export interface FujifilmRecipe {
|
||||
}
|
||||
bwAdjustment: number
|
||||
bwMagentaGreen: number
|
||||
}
|
||||
}>;
|
||||
|
||||
const DEFAULT_GRAIN_EFFECT = {
|
||||
roughness: 'off',
|
||||
@ -75,7 +75,7 @@ export const processNoiseReductionLegacy = (value: number) => {
|
||||
switch (value) {
|
||||
case 0x40: return 'low';
|
||||
case 0x80: return 'normal';
|
||||
default: return 'n/a';
|
||||
default: return 'n/a';
|
||||
}
|
||||
};
|
||||
|
||||
@ -119,7 +119,7 @@ export const processWeakStrong = (value: number): WeakStrong => {
|
||||
|
||||
export const processGrainEffectSize = (
|
||||
value: number,
|
||||
): FujifilmRecipe['grainEffect']['size'] => {
|
||||
): Required<FujifilmRecipe>['grainEffect']['size'] => {
|
||||
switch (value) {
|
||||
case 16: return 'small';
|
||||
case 32: return 'large';
|
||||
@ -155,8 +155,8 @@ export const processWhiteBalanceComponent = (value: number) => value / 20;
|
||||
|
||||
export const getFujifilmRecipeFromMakerNote = (
|
||||
bytes: Buffer,
|
||||
): Partial<FujifilmRecipe> => {
|
||||
const recipe: Partial<FujifilmRecipe> = {};
|
||||
): FujifilmRecipe => {
|
||||
const recipe: FujifilmRecipe = {};
|
||||
|
||||
parseFujifilmMakerNote(
|
||||
bytes,
|
||||
@ -177,7 +177,7 @@ export const getFujifilmRecipeFromMakerNote = (
|
||||
case TAG_ID_NOISE_REDUCTION:
|
||||
recipe.highISONoiseReduction = processNoiseReduction(numbers[0]);
|
||||
break;
|
||||
case TAG_ID_NOISE_REDUCTION_LEGACY:
|
||||
case TAG_ID_NOISE_REDUCTION_BASIC:
|
||||
recipe.noiseReductionLegacy =
|
||||
processNoiseReductionLegacy(numbers[0]);
|
||||
break;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user