From 62a681a4247c53158e316d9417b8c1b858ea02e5 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Wed, 19 Feb 2025 20:34:31 -0600 Subject: [PATCH] Display basic fujifilm recipes --- src/photo/PhotoLarge.tsx | 14 +++-- src/photo/PhotoRecipe.tsx | 93 ++++++++++++++++++++++++++++++++ src/photo/form/index.ts | 4 +- src/photo/index.ts | 9 ++-- src/photo/server.ts | 2 +- src/platforms/fujifilm/recipe.ts | 16 +++--- 6 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 src/photo/PhotoRecipe.tsx diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index 7f5beb3a..c76f77cc 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -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({
  • {photo.exposureCompensationFormatted ?? '0ev'}
  • {showSimulation && photo.filmSimulation && - } + + : undefined + }> + + } }
    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
    +
    + Fujifilm Recipe +
    +
    + {dynamicRange !== undefined && <> +
    DR
    +
    {dynamicRange}
    + } + {highlight !== undefined && <> +
    Highlight
    +
    {addSign(highlight)}
    + } + {shadow !== undefined && <> +
    Shadow
    +
    {addSign(shadow)}
    + } + {color !== undefined && <> +
    Color
    +
    {addSign(color)}
    + } + {highISONoiseReduction !== undefined && <> +
    High ISO NR
    +
    {addSign(highISONoiseReduction)}
    + } + {noiseReductionLegacy !== undefined && <> +
    NR
    +
    {noiseReductionLegacy}
    + } + {sharpness !== undefined && <> +
    Sharpness
    +
    {addSign(sharpness)}
    + } + {clarity !== undefined && <> +
    Clarity
    +
    {addSign(clarity)}
    + } + {grainEffect !== undefined && <> +
    Grain
    +
    {grainEffect.roughness} / {grainEffect.size}
    + } + {colorChromeEffect !== undefined && <> +
    Chrome
    +
    {colorChromeEffect}
    + } + {colorChromeFXBlue !== undefined && <> +
    Chrome FX Blue
    +
    {colorChromeFXBlue}
    + } + {whiteBalance !== undefined && <> +
    White Balance
    +
    +
    {whiteBalance.type}
    +
    + {addSign(whiteBalance.red)} / {addSign(whiteBalance.blue)} +
    +
    + } + {bwAdjustment !== undefined && <> +
    BW
    +
    {addSign(bwAdjustment)}
    + } + {bwMagentaGreen !== undefined && <> +
    BW MG
    +
    {addSign(bwMagentaGreen)}
    + } +
    +
    ; +} diff --git a/src/photo/form/index.ts b/src/photo/form/index.ts index 5311e546..cfe37de3 100644 --- a/src/photo/form/index.ts +++ b/src/photo/form/index.ts @@ -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, ): Omit< Record, 'takenAt' | 'takenAtNaive' diff --git a/src/photo/index.ts b/src/photo/index.ts index 47b35a68..57c3f93e 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -91,16 +91,15 @@ export interface PhotoDbInsert extends PhotoExif { // Raw db response export interface PhotoDb extends - Omit { + Omit { updatedAt: Date createdAt: Date takenAt: Date tags: string[] - fujifilmRecipe?: Partial } // Parsed db response -export interface Photo extends PhotoDb { +export interface Photo extends Omit { 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), }; diff --git a/src/photo/server.ts b/src/photo/server.ts index 79d8fd76..85eb594a 100644 --- a/src/photo/server.ts +++ b/src/photo/server.ts @@ -51,7 +51,7 @@ export const extractImageDataFromBlobPath = async ( let exifData: ExifData | undefined; let filmSimulation: FilmSimulation | undefined; - let recipe: Partial | undefined; + let recipe: FujifilmRecipe | undefined; let blurData: string | undefined; let imageResizedBase64: string | undefined; let shouldStripGpsData = false; diff --git a/src/platforms/fujifilm/recipe.ts b/src/platforms/fujifilm/recipe.ts index 8a75258d..1e5ed518 100644 --- a/src/platforms/fujifilm/recipe.ts +++ b/src/platforms/fujifilm/recipe.ts @@ -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['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 => { - const recipe: Partial = {}; +): 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;