Refine EXIF text formatting
This commit is contained in:
parent
883fa7eeb3
commit
6cd0d0b285
29
__tests__/exif.test.ts
Normal file
29
__tests__/exif.test.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { formatExposureCompensation, formatExposureTime } from '@/utility/exif';
|
||||
|
||||
describe('EXIF', () => {
|
||||
describe('formats', () => {
|
||||
it('exposure time', () => {
|
||||
expect(formatExposureTime(0)).toBe(undefined);
|
||||
expect(formatExposureTime(undefined)).toBe(undefined);
|
||||
expect(formatExposureTime(0.5)).toBe('1/2s');
|
||||
expect(formatExposureTime(0.1)).toBe('1/10s');
|
||||
expect(formatExposureTime(0.01)).toBe('1/100s');
|
||||
expect(formatExposureTime(1)).toBe('1s');
|
||||
expect(formatExposureTime(1.5)).toBe('1.5s');
|
||||
});
|
||||
it('exposure compensation', () => {
|
||||
expect(formatExposureCompensation(-1)).toBe('-1ev');
|
||||
expect(formatExposureCompensation(0)).toBe(undefined);
|
||||
expect(formatExposureCompensation(0.33)).toBe('+1/3ev');
|
||||
expect(formatExposureCompensation(0.333)).toBe('+1/3ev');
|
||||
expect(formatExposureCompensation(0.5)).toBe('+1/2ev');
|
||||
expect(formatExposureCompensation(0.66)).toBe('+2/3ev');
|
||||
expect(formatExposureCompensation(0.67)).toBe('+2/3ev');
|
||||
expect(formatExposureCompensation(0.015625)).toBe('+1/64ev');
|
||||
expect(formatExposureCompensation(-0.015625)).toBe('-1/64ev');
|
||||
expect(formatExposureCompensation(1)).toBe('+1ev');
|
||||
expect(formatExposureCompensation(1.5)).toBe('+1 1/2ev');
|
||||
expect(formatExposureCompensation(1.9960938)).toBe('+2ev');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -90,17 +90,20 @@ export default function PhotoLarge({
|
||||
{photo.focalLengthIn35MmFormatFormatted &&
|
||||
<>
|
||||
{' '}
|
||||
<span className={cc(
|
||||
'text-gray-400/80',
|
||||
'dark:text-gray-400/50',
|
||||
)}>
|
||||
<span
|
||||
className={cc(
|
||||
'text-gray-400/80',
|
||||
'dark:text-gray-400/50',
|
||||
)}
|
||||
title="35mm equivalent"
|
||||
>
|
||||
{photo.focalLengthIn35MmFormatFormatted}
|
||||
</span>
|
||||
</>}
|
||||
</li>
|
||||
<li>{photo.fNumberFormatted}</li>
|
||||
<li>{photo.isoFormatted}</li>
|
||||
<li>{photo.exposureTimeFormatted}</li>
|
||||
<li>{photo.isoFormatted}</li>
|
||||
<li>{photo.exposureCompensationFormatted ?? '—'}</li>
|
||||
</ul>}
|
||||
<div className={cc(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ExifData } from 'ts-exif-parser';
|
||||
import { formatNumberToFraction } from './number';
|
||||
|
||||
const OFFSET_REGEX = /[+-]\d\d:\d\d/;
|
||||
|
||||
@ -18,33 +19,19 @@ export const formatAperture = (aperture?: number) =>
|
||||
export const formatIso = (iso?: number) =>
|
||||
iso ? `ISO ${iso}` : undefined;
|
||||
|
||||
export const formatExposureTime = (exposureTime?: number) =>
|
||||
exposureTime
|
||||
? `Shutter 1/${Math.floor(1 / (exposureTime ?? 1))}`
|
||||
export const formatExposureTime = (exposureTime = 0) =>
|
||||
exposureTime > 0
|
||||
? exposureTime < 1
|
||||
? `1/${Math.floor(1 / exposureTime)}s`
|
||||
: `${exposureTime}s`
|
||||
: undefined;
|
||||
|
||||
const fractionForDecimal = (decimal: number, fractionCharacter?: boolean) => {
|
||||
switch (Math.abs(Math.floor(decimal * 100))) {
|
||||
case 33:
|
||||
return fractionCharacter ? '⅓' : '1/3';
|
||||
case 50:
|
||||
return fractionCharacter ? '½' : '1/2';
|
||||
case 66:
|
||||
case 67:
|
||||
return fractionCharacter ? '⅔' : '2/3';
|
||||
}
|
||||
};
|
||||
|
||||
export const formatExposureCompensation = (exposureCompensation?: number) => {
|
||||
if (
|
||||
exposureCompensation &&
|
||||
Math.abs(exposureCompensation) >= 0.33
|
||||
Math.abs(exposureCompensation) > 0.01
|
||||
) {
|
||||
const decimal = exposureCompensation % 1;
|
||||
const whole = Math.abs(exposureCompensation - decimal);
|
||||
const fraction = fractionForDecimal(decimal);
|
||||
const sign = exposureCompensation > 0 ? '+' : '-';
|
||||
return `${sign}${whole ? `${whole} ` : ''}${fraction ?? ''} EV`;
|
||||
return `${formatNumberToFraction(exposureCompensation)}ev`;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -5,3 +5,46 @@ export const toFixedNumber = (
|
||||
const pow = Math.pow(base ?? 10, digits);
|
||||
return Math.round(number * pow) / pow;
|
||||
};
|
||||
|
||||
const gcd = (a: number, b: number): number => {
|
||||
if (b <= 0.0000001) {
|
||||
return a;
|
||||
} else {
|
||||
return gcd(b, a % b);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDecimalToFraction = (decimal: number) => {
|
||||
if (Math.abs(decimal - 0.33) < 0.011) {
|
||||
return '1/3';
|
||||
} else if (Math.abs(decimal - 0.66) <= 0.011) {
|
||||
return '2/3';
|
||||
} else {
|
||||
const length = decimal.toString().length - 2;
|
||||
|
||||
let denominator = Math.pow(10, length);
|
||||
let numerator = decimal * denominator;
|
||||
|
||||
const divisor = gcd(numerator, denominator);
|
||||
|
||||
numerator /= divisor;
|
||||
denominator /= divisor;
|
||||
|
||||
return `${Math.floor(numerator)}/${Math.floor(denominator)}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const formatNumberToFraction = (number: number) => {
|
||||
const decimal = (1 - number % 1) > 0.01
|
||||
? number % 1
|
||||
: 0;
|
||||
const integer = Math.round(Math.abs(number - decimal));
|
||||
const fraction = decimal !== 0
|
||||
? formatDecimalToFraction(Math.abs(decimal))
|
||||
: '';
|
||||
const sign = number > 0 ? '+' : '-';
|
||||
const whole = integer > 0
|
||||
? fraction ? `${integer} ` : integer
|
||||
: '';
|
||||
return `${sign}${whole}${fraction}`;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user