diff --git a/__tests__/exif.test.ts b/__tests__/exif.test.ts new file mode 100644 index 00000000..d7bcb77d --- /dev/null +++ b/__tests__/exif.test.ts @@ -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'); + }); + }); +}); diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index 82d54490..ce16d094 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -90,17 +90,20 @@ export default function PhotoLarge({ {photo.focalLengthIn35MmFormatFormatted && <> {' '} - + {photo.focalLengthIn35MmFormatFormatted} }
  • {photo.fNumberFormatted}
  • -
  • {photo.isoFormatted}
  • {photo.exposureTimeFormatted}
  • +
  • {photo.isoFormatted}
  • {photo.exposureCompensationFormatted ?? '—'}
  • }
    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; } diff --git a/src/utility/number.ts b/src/utility/number.ts index edaf465d..e0653d9e 100644 --- a/src/utility/number.ts +++ b/src/utility/number.ts @@ -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}`; +};