Create initial fujifilm recipe type

This commit is contained in:
Sam Becker 2025-02-18 22:53:18 -06:00
parent d973b95d00
commit 8249e2929b
12 changed files with 90 additions and 63 deletions

View File

@ -2,9 +2,10 @@
import SiteGrid from '@/components/SiteGrid';
import { clsx } from 'clsx/lite';
import { FILM_SIMULATION_FORM_INPUT_OPTIONS } from '@/platforms/fujifilm';
import PhotoFilmSimulation from
'@/simulation/PhotoFilmSimulation';
import {
FILM_SIMULATION_FORM_INPUT_OPTIONS,
} from '@/platforms/fujifilm/simulation';
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
import { useEffect, useState } from 'react';
export default function FilmPage() {

View File

@ -1,6 +1,7 @@
import { FILM_SIMULATION_FORM_INPUT_OPTIONS } from '@/platforms/fujifilm';
import PhotoFilmSimulation from
'@/simulation/PhotoFilmSimulation';
import {
FILM_SIMULATION_FORM_INPUT_OPTIONS,
} from '@/platforms/fujifilm/simulation';
import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
export default function FilmPage() {
return (

View File

@ -18,7 +18,7 @@ import { formatCount, formatCountDescriptive } from '@/utility/string';
import PhotoFilmSimulationIcon from '@/simulation/PhotoFilmSimulationIcon';
import { IoMdCamera } from 'react-icons/io';
import { ADMIN_DEBUG_TOOLS_ENABLED, SHOW_FILM_SIMULATIONS } from './config';
import { labelForFilmSimulation } from '@/platforms/fujifilm';
import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation';
import { getUniqueFocalLengths } from '@/photo/db/query';
import { formatFocalLength } from '@/focal';
import { TbCone } from 'react-icons/tb';

View File

@ -4,7 +4,7 @@ import ImagePhotoGrid from './components/ImagePhotoGrid';
import ImageContainer from './components/ImageContainer';
import {
labelForFilmSimulation,
} from '@/platforms/fujifilm';
} from '@/platforms/fujifilm/simulation';
import PhotoFilmSimulationIcon from
'@/simulation/PhotoFilmSimulationIcon';
import { FilmSimulation } from '@/simulation';

View File

@ -18,11 +18,11 @@ import { convertStringToArray } from '@/utility/string';
import { generateNanoid } from '@/utility/nanoid';
import {
FILM_SIMULATION_FORM_INPUT_OPTIONS,
MAKE_FUJIFILM,
} from '@/platforms/fujifilm';
} from '@/platforms/fujifilm/simulation';
import { FilmSimulation } from '@/simulation';
import { GEO_PRIVACY_ENABLED } from '@/app/config';
import { TAG_FAVS, getValidationMessageForTags } from '@/tag';
import { MAKE_FUJIFILM } from '@/platforms/fujifilm';
type VirtualFields = 'favorite';

View File

@ -5,8 +5,7 @@ import {
import { convertExifToFormData } from '@/photo/form';
import {
getFujifilmSimulationFromMakerNote,
isExifForFujifilm,
} from '@/platforms/fujifilm';
} from '@/platforms/fujifilm/simulation';
import { ExifData, ExifParserFactory } from 'ts-exif-parser';
import { PhotoFormData } from './form';
import { FilmSimulation } from '@/simulation';
@ -15,6 +14,7 @@ import {
GEO_PRIVACY_ENABLED,
PRESERVE_ORIGINAL_UPLOADS,
} from '@/app/config';
import { isExifForFujifilm } from '@/platforms/fujifilm';
const IMAGE_WIDTH_RESIZE = 200;
const IMAGE_WIDTH_BLUR = 200;

View File

@ -0,0 +1,48 @@
// MakerNote tag IDs and values referenced from:
// github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/FujiFilm.pm
import type { ExifData } from 'ts-exif-parser';
export const MAKE_FUJIFILM = 'FUJIFILM';
const BYTE_INDEX_TAG_COUNT = 12;
const BYTE_INDEX_FIRST_TAG = 14;
const BYTES_PER_TAG = 12;
const BYTE_OFFSET_TAG_TYPE = 2;
const BYTE_OFFSET_TAG_VALUE = 8;
export const TAG_ID_SATURATION = 0x1003;
export const TAG_ID_FILM_MODE = 0x1401;
export const isExifForFujifilm = (data: ExifData) =>
data.tags?.Make === MAKE_FUJIFILM;
export const parseFujifilmMakerNote = (
bytes: Buffer,
valueForTagUInt: (tagId: number, value: number) => void,
) => {
const tagCount = bytes.readUint16LE(BYTE_INDEX_TAG_COUNT);
for (let i = 0; i < tagCount; i++) {
const index = BYTE_INDEX_FIRST_TAG + i * BYTES_PER_TAG;
if (index + BYTES_PER_TAG < bytes.length) {
const tagId = bytes.readUInt16LE(index);
const tagType = bytes.readUInt16LE(index + BYTE_OFFSET_TAG_TYPE);
switch (tagType) {
// UInt16
case 3:
valueForTagUInt(
tagId,
bytes.readUInt16LE(index + BYTE_OFFSET_TAG_VALUE),
);
break;
// UInt32
case 4:
valueForTagUInt(
tagId,
bytes.readUInt32LE(index + BYTE_OFFSET_TAG_VALUE),
);
break;
}
}
}
};

View File

@ -0,0 +1,20 @@
export interface FujifilmRecipe {
dynamicRange: number
highlight: number
shadow: number
color: number
noiseReduction: number
sharpening: number
clarity: number
grainEffect: {
type: 'strong' | 'medium' | 'weak'
size: 'small' | 'large'
}
colorChromeEffect: 'strong' | 'medium' | 'weak'
colorChromeEffectBlue: 'off' | 'weak' | 'strong'
whiteBalance: {
type: string
red: number
blue: number
}
}

View File

@ -1,18 +1,8 @@
// MakerNote tag IDs and values referenced from:
// github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/FujiFilm.pm
import type { ExifData } from 'ts-exif-parser';
export const MAKE_FUJIFILM = 'FUJIFILM';
const BYTE_INDEX_TAG_COUNT = 12;
const BYTE_INDEX_FIRST_TAG = 14;
const BYTES_PER_TAG = 12;
const BYTE_OFFSET_TAG_TYPE = 2;
const BYTE_OFFSET_TAG_VALUE = 8;
const TAG_ID_SATURATION = 0x1003;
const TAG_ID_FILM_MODE = 0x1401;
import {
TAG_ID_FILM_MODE,
parseFujifilmMakerNote,
TAG_ID_SATURATION,
} from '.';
type FujifilmSimulationFromSaturation =
'monochrome' |
@ -46,9 +36,6 @@ export type FujifilmSimulation =
FujifilmSimulationFromSaturation |
FujifilmMode;
export const isExifForFujifilm = (data: ExifData) =>
data.tags?.Make === MAKE_FUJIFILM;
const getFujifilmSimulationFromSaturation = (
value?: number,
): FujifilmSimulationFromSaturation | undefined => {
@ -231,36 +218,6 @@ export const FILM_SIMULATION_FORM_INPUT_OPTIONS = Object
export const labelForFilmSimulation = (simulation: FujifilmSimulation) =>
FILM_SIMULATION_LABELS[simulation];
const parseFujifilmMakerNote = (
bytes: Buffer,
valueForTagUInt: (tagId: number, value: number) => void,
) => {
const tagCount = bytes.readUint16LE(BYTE_INDEX_TAG_COUNT);
for (let i = 0; i < tagCount; i++) {
const index = BYTE_INDEX_FIRST_TAG + i * BYTES_PER_TAG;
if (index + BYTES_PER_TAG < bytes.length) {
const tagId = bytes.readUInt16LE(index);
const tagType = bytes.readUInt16LE(index + BYTE_OFFSET_TAG_TYPE);
switch (tagType) {
// UInt16
case 3:
valueForTagUInt(
tagId,
bytes.readUInt16LE(index + BYTE_OFFSET_TAG_VALUE),
);
break;
// UInt32
case 4:
valueForTagUInt(
tagId,
bytes.readUInt32LE(index + BYTE_OFFSET_TAG_VALUE),
);
break;
}
}
}
};
export const getFujifilmSimulationFromMakerNote = (
bytes: Buffer,
): FujifilmSimulation | undefined => {

View File

@ -1,4 +1,4 @@
import { labelForFilmSimulation } from '@/platforms/fujifilm';
import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation';
import PhotoFilmSimulationIcon from './PhotoFilmSimulationIcon';
import { pathForFilmSimulation } from '@/app/paths';
import { FilmSimulation } from '.';

View File

@ -1,5 +1,5 @@
/* eslint-disable max-len */
import { labelForFilmSimulation } from '@/platforms/fujifilm';
import { labelForFilmSimulation } from '@/platforms/fujifilm/simulation';
import { CSSProperties } from 'react';
import { FilmSimulation } from '.';

View File

@ -11,7 +11,7 @@ import {
import {
FujifilmSimulation,
labelForFilmSimulation,
} from '@/platforms/fujifilm';
} from '@/platforms/fujifilm/simulation';
export type FilmSimulation = FujifilmSimulation;