259 lines
5.9 KiB
TypeScript
259 lines
5.9 KiB
TypeScript
import { parseFujifilmMakerNote } from './server';
|
|
|
|
const TAG_ID_SATURATION = 0x1003;
|
|
const TAG_ID_FILM_MODE = 0x1401;
|
|
|
|
type FujifilmSimulationFromSaturation =
|
|
'monochrome' |
|
|
'monochrome-ye' |
|
|
'monochrome-r' |
|
|
'monochrome-g' |
|
|
'sepia' |
|
|
'acros' |
|
|
'acros-ye' |
|
|
'acros-r' |
|
|
'acros-g';
|
|
|
|
type FujifilmMode =
|
|
'provia' |
|
|
'portrait' |
|
|
'portrait-saturation' |
|
|
'astia' |
|
|
'portrait-sharpness' |
|
|
'portrait-ex' |
|
|
'velvia' |
|
|
'pro-neg-std' |
|
|
'pro-neg-hi' |
|
|
'classic-chrome' |
|
|
'eterna' |
|
|
'classic-neg' |
|
|
'eterna-bleach-bypass' |
|
|
'nostalgic-neg' |
|
|
'reala';
|
|
|
|
export type FujifilmSimulation =
|
|
FujifilmSimulationFromSaturation |
|
|
FujifilmMode;
|
|
|
|
const getFujifilmSimulationFromSaturation = (
|
|
value?: number,
|
|
): FujifilmSimulationFromSaturation | undefined => {
|
|
switch (value) {
|
|
case 0x300: return 'monochrome';
|
|
case 0x301: return 'monochrome-r';
|
|
case 0x302: return 'monochrome-ye';
|
|
case 0x303: return 'monochrome-g';
|
|
case 0x310: return 'sepia';
|
|
case 0x500: return 'acros';
|
|
case 0x501: return 'acros-r';
|
|
case 0x502: return 'acros-ye';
|
|
case 0x503: return 'acros-g';
|
|
}
|
|
};
|
|
|
|
const getFujifilmMode = (
|
|
value?: number,
|
|
): FujifilmMode | undefined => {
|
|
switch (value) {
|
|
case 0x000: return 'provia';
|
|
case 0x100: return 'portrait';
|
|
case 0x110: return 'portrait-saturation';
|
|
case 0x120: return 'astia'; // can be encoded as 'portrait-skin-tone'
|
|
case 0x130: return 'portrait-sharpness';
|
|
case 0x300: return 'portrait-ex';
|
|
case 0x200:
|
|
case 0x400: return 'velvia';
|
|
case 0x500: return 'pro-neg-std';
|
|
case 0x501: return 'pro-neg-hi';
|
|
case 0x600: return 'classic-chrome';
|
|
case 0x700: return 'eterna';
|
|
case 0x800: return 'classic-neg';
|
|
case 0x900: return 'eterna-bleach-bypass';
|
|
case 0xa00: return 'nostalgic-neg';
|
|
case 0xb00: return 'reala';
|
|
}
|
|
};
|
|
|
|
interface FujifilmSimulationLabel {
|
|
small: string
|
|
medium: string
|
|
large: string
|
|
}
|
|
|
|
const FUJIFILM_SIMULATION_LABELS: Record<
|
|
FujifilmSimulation,
|
|
FujifilmSimulationLabel
|
|
> = {
|
|
'monochrome': {
|
|
small: 'Monochrome',
|
|
medium: 'Monochrome',
|
|
large: 'Monochrome',
|
|
},
|
|
'monochrome-ye': {
|
|
small: 'Monochrome+Ye',
|
|
medium: 'Monochrome+Ye',
|
|
large: 'Monochrome + Yellow Filter',
|
|
},
|
|
'monochrome-r': {
|
|
small: 'Monochrome+R',
|
|
medium: 'Monochrome+R',
|
|
large: 'Monochrome + Red Filter',
|
|
},
|
|
'monochrome-g': {
|
|
small: 'Monochrome+G',
|
|
medium: 'Monochrome+G',
|
|
large: 'Monochrome + Green Filter',
|
|
},
|
|
'sepia': {
|
|
small: 'Sepia',
|
|
medium: 'Sepia',
|
|
large: 'Sepia',
|
|
},
|
|
'acros': {
|
|
small: 'ACROS',
|
|
medium: 'ACROS',
|
|
large: 'ACROS',
|
|
},
|
|
'acros-ye': {
|
|
small: 'ACROS+Ye',
|
|
medium: 'ACROS+Ye',
|
|
large: 'ACROS + Yellow Filter',
|
|
},
|
|
'acros-r': {
|
|
small: 'ACROS+R',
|
|
medium: 'ACROS+R',
|
|
large: 'ACROS + Red Filter',
|
|
},
|
|
'acros-g': {
|
|
small: 'ACROS+G',
|
|
medium: 'ACROS+G',
|
|
large: 'ACROS + Green Filter',
|
|
},
|
|
'provia': {
|
|
small: 'PROVIA',
|
|
medium: 'PROVIA/Std',
|
|
large: 'PROVIA / Standard',
|
|
},
|
|
'portrait': {
|
|
small: 'Portrait',
|
|
medium: 'Portrait',
|
|
large: 'Studio Portrait',
|
|
},
|
|
'portrait-saturation': {
|
|
small: 'Portrait+Sat.',
|
|
medium: 'Portrait+Sat.',
|
|
large: 'Studio Portrait + Enhanced Saturation',
|
|
},
|
|
'astia': {
|
|
small: 'ASTIA',
|
|
medium: 'ASTIA/Soft',
|
|
large: 'ASTIA / Soft',
|
|
},
|
|
'portrait-sharpness': {
|
|
small: 'Portrait+Sharp.',
|
|
medium: 'Portrait+Sharp.',
|
|
large: 'Studio Portrait + Enhanced Sharpness',
|
|
},
|
|
'portrait-ex': {
|
|
small: 'Portrait+Ex',
|
|
medium: 'Portrait+Ex',
|
|
large: 'Studio Portrait + Ex',
|
|
},
|
|
'velvia': {
|
|
small: 'Velvia',
|
|
medium: 'Velvia/Vivid',
|
|
large: 'Velvia / Vivid',
|
|
},
|
|
'pro-neg-std': {
|
|
small: 'PRO Neg. Std',
|
|
medium: 'PRO Neg. Std',
|
|
large: 'PRO Neg. Std',
|
|
},
|
|
'pro-neg-hi': {
|
|
small: 'PRO Neg. Hi',
|
|
medium: 'PRO Neg. Hi',
|
|
large: 'PRO Neg. Hi',
|
|
},
|
|
'classic-chrome': {
|
|
small: 'Classic Chrome',
|
|
medium: 'Classic Chrome',
|
|
large: 'Classic Chrome',
|
|
},
|
|
'eterna': {
|
|
small: 'ETERNA',
|
|
medium: 'ETERNA/Cinema',
|
|
large: 'ETERNA / Cinema',
|
|
},
|
|
'classic-neg': {
|
|
small: 'Classic Neg.',
|
|
medium: 'Classic Neg.',
|
|
large: 'Classic Neg.',
|
|
},
|
|
'eterna-bleach-bypass': {
|
|
small: 'ETERNA Bypass',
|
|
medium: 'ETERNA Bypass',
|
|
large: 'ETERNA Bleach Bypass',
|
|
},
|
|
'nostalgic-neg': {
|
|
small: 'Nostalgic Neg.',
|
|
medium: 'Nostalgic Neg.',
|
|
large: 'Nostalgic Neg.',
|
|
},
|
|
'reala': {
|
|
small: 'REALA',
|
|
medium: 'REALA ACE',
|
|
large: 'REALA ACE',
|
|
},
|
|
};
|
|
|
|
export const FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS = Object
|
|
.entries(FUJIFILM_SIMULATION_LABELS)
|
|
.map(([value, label]) => (
|
|
{ value, label: label.large } as
|
|
{ value: FujifilmSimulation, label: string }
|
|
))
|
|
.sort((a, b) => a.label.localeCompare(b.label));
|
|
|
|
const ALL_POSSIBLE_FUJIFILM_SIMULATION_LABELS = Object
|
|
.values(FUJIFILM_SIMULATION_LABELS)
|
|
.flatMap(({ small, medium, large }) => [
|
|
small.toLocaleLowerCase(),
|
|
medium.toLocaleLowerCase(),
|
|
large.toLocaleLowerCase(),
|
|
]);
|
|
|
|
export const isStringFujifilmSimulation = (film?: string) =>
|
|
film !== undefined &&
|
|
Object.keys(FUJIFILM_SIMULATION_LABELS).includes(film);
|
|
|
|
export const isStringFujifilmSimulationLabel = (film: string) =>
|
|
ALL_POSSIBLE_FUJIFILM_SIMULATION_LABELS.includes(film.toLocaleLowerCase());
|
|
|
|
export const labelForFujifilmSimulation = (film: FujifilmSimulation) =>
|
|
FUJIFILM_SIMULATION_LABELS[film];
|
|
|
|
export const getFujifilmSimulationFromMakerNote = (
|
|
bytes: Buffer,
|
|
): FujifilmSimulation | undefined => {
|
|
let filmModeFromSaturation: FujifilmSimulationFromSaturation | undefined;
|
|
let filmMode: FujifilmMode | undefined;
|
|
|
|
parseFujifilmMakerNote(
|
|
bytes,
|
|
(tag, numbers) => {
|
|
switch (tag) {
|
|
case TAG_ID_SATURATION:
|
|
filmModeFromSaturation =
|
|
getFujifilmSimulationFromSaturation(numbers[0]);
|
|
break;
|
|
case TAG_ID_FILM_MODE:
|
|
filmMode =
|
|
getFujifilmMode(numbers[0]);
|
|
break;
|
|
}
|
|
},
|
|
);
|
|
|
|
return filmModeFromSaturation ?? filmMode;
|
|
};
|