diff --git a/src/app/(static)/film/[simulation]/image/route.tsx b/src/app/(static)/film/[simulation]/image/route.tsx index fafe45ad..7b41da1d 100644 --- a/src/app/(static)/film/[simulation]/image/route.tsx +++ b/src/app/(static)/film/[simulation]/image/route.tsx @@ -1,7 +1,7 @@ import { auth } from '@/auth'; import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache'; import { - IMAGE_OG_SMALL_SIZE, + IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_TAG, } from '@/photo/image-response'; import FilmSimulationImageResponse from @@ -28,7 +28,7 @@ export async function GET( getImageCacheHeadersForAuth(await auth()), ]); - const { width, height } = IMAGE_OG_SMALL_SIZE; + const { width, height } = IMAGE_OG_DIMENSION_SMALL; return new ImageResponse( , diff --git a/src/app/(static)/p/[photoId]/image/route.tsx b/src/app/(static)/p/[photoId]/image/route.tsx index ae887b7e..da60e6ab 100644 --- a/src/app/(static)/p/[photoId]/image/route.tsx +++ b/src/app/(static)/p/[photoId]/image/route.tsx @@ -1,6 +1,6 @@ import { auth } from '@/auth'; import { getImageCacheHeadersForAuth, getPhotoCached } from '@/cache'; -import { IMAGE_OG_SIZE } from '@/photo/image-response'; +import { IMAGE_OG_DIMENSION } from '@/photo/image-response'; import PhotoImageResponse from '@/photo/image-response/PhotoImageResponse'; import { getIBMPlexMonoMedium } from '@/site/font'; import { ImageResponse } from 'next/og'; @@ -23,7 +23,7 @@ export async function GET( if (!photo) { return new Response('Photo not found', { status: 404 }); } - const { width, height } = IMAGE_OG_SIZE; + const { width, height } = IMAGE_OG_DIMENSION; return new ImageResponse( , diff --git a/src/app/(static)/shot-on/[camera]/image/route.tsx b/src/app/(static)/shot-on/[camera]/image/route.tsx index a453f2a4..21473e2d 100644 --- a/src/app/(static)/shot-on/[camera]/image/route.tsx +++ b/src/app/(static)/shot-on/[camera]/image/route.tsx @@ -2,7 +2,7 @@ import { auth } from '@/auth'; import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache'; import { getCameraFromKey } from '@/camera'; import { - IMAGE_OG_SMALL_SIZE, + IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_TAG, } from '@/photo/image-response'; import CameraImageResponse from '@/photo/image-response/CameraImageResponse'; @@ -30,7 +30,7 @@ export async function GET( getImageCacheHeadersForAuth(await auth()), ]); - const { width, height } = IMAGE_OG_SMALL_SIZE; + const { width, height } = IMAGE_OG_DIMENSION_SMALL; return new ImageResponse( {loadingState === 'loading' && { @@ -99,29 +104,36 @@ export default function PhotoForm({ return ( - - - {debugBlur && formData.blurData && - } + + + + {debugBlur && formData.blurData && + } + => ({ - aspectRatio: ( - (data.imageSize?.width ?? 3.0) / - (data.imageSize?.height ?? 2.0) - ).toString(), + aspectRatio: getAspectRatioFromExif(data).toString(), make: data.tags?.Make, model: data.tags?.Model, focalLength: data.tags?.FocalLength?.toString(), diff --git a/src/photo/image-response/index.ts b/src/photo/image-response/index.ts index 4721e332..37fd8a6a 100644 --- a/src/photo/image-response/index.ts +++ b/src/photo/image-response/index.ts @@ -1,35 +1,36 @@ import { NextImageSize } from '@/services/next-image'; +import { getDimensionsFromSize } from '@/utility/size'; export const MAX_PHOTOS_TO_SHOW_OG = 12; export const MAX_PHOTOS_TO_SHOW_PER_TAG = 6; export const MAX_PHOTOS_TO_SHOW_TEMPLATE = 16; export const MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT = 12; +interface OGImageDimension { + width: NextImageSize + height: number + aspectRatio: number +} + // 16:9 og image ratio const IMAGE_OG_RATIO = 16 / 9; const IMAGE_OG_WIDTH: NextImageSize = 1080; -const IMAGE_OG_HEIGHT = IMAGE_OG_WIDTH * (1 / IMAGE_OG_RATIO); -export const IMAGE_OG_SIZE = { - width: IMAGE_OG_WIDTH, - height: IMAGE_OG_HEIGHT, - ratio: IMAGE_OG_RATIO, -}; +export const IMAGE_OG_DIMENSION = getDimensionsFromSize( + IMAGE_OG_WIDTH, + IMAGE_OG_RATIO, +) as OGImageDimension; // 16:9 og image ratio, small const IMAGE_OG_SMALL_WIDTH: NextImageSize = 828; -const IMAGE_OG_SMALL_HEIGHT = IMAGE_OG_SMALL_WIDTH * (1 / IMAGE_OG_RATIO); -export const IMAGE_OG_SMALL_SIZE = { - width: IMAGE_OG_SMALL_WIDTH, - height: IMAGE_OG_SMALL_HEIGHT, - ratio: IMAGE_OG_RATIO, -}; +export const IMAGE_OG_DIMENSION_SMALL = getDimensionsFromSize( + IMAGE_OG_SMALL_WIDTH, + IMAGE_OG_RATIO, +) as OGImageDimension; -// 3:2 og grid ratio -const GRID_OG_RATIO = 1.33; +// 4:3 og grid ratio +const GRID_OG_RATIO = 4 / 3; const GRID_OG_WIDTH: NextImageSize = 2048; -const GRID_OG_HEIGHT = GRID_OG_WIDTH * (1 / GRID_OG_RATIO); -export const GRID_OG_SIZE = { - width: GRID_OG_WIDTH, - height: GRID_OG_HEIGHT, - ratio: GRID_OG_RATIO, -}; +export const GRID_OG_DIMENSION = getDimensionsFromSize( + GRID_OG_WIDTH, + GRID_OG_RATIO, +) as OGImageDimension; diff --git a/src/utility/exif.ts b/src/utility/exif.ts index 684ee13f..c83b8ac6 100644 --- a/src/utility/exif.ts +++ b/src/utility/exif.ts @@ -1,4 +1,4 @@ -import type { ExifData } from 'ts-exif-parser'; +import { OrientationTypes, type ExifData } from 'ts-exif-parser'; import { formatNumberToFraction } from './number'; const OFFSET_REGEX = /[+-]\d\d:\d\d/; @@ -10,6 +10,27 @@ export const getOffsetFromExif = (data: ExifData) => OFFSET_REGEX.test(value) ) as string | undefined; +export const getAspectRatioFromExif = (data: ExifData): number => { + // Using '||' operator to handle `Orientation` unexpectedly being '0' + const orientation = data.tags?.Orientation || OrientationTypes.TOP_LEFT; + + const width = data.imageSize?.width ?? 3.0; + const height = data.imageSize?.height ?? 2.0; + + switch (orientation) { + case OrientationTypes.TOP_LEFT: + case OrientationTypes.TOP_RIGHT: + case OrientationTypes.BOTTOM_RIGHT: + case OrientationTypes.BOTTOM_LEFT: + case OrientationTypes.LEFT_TOP: + case OrientationTypes.RIGHT_BOTTOM: + return width / height; + case OrientationTypes.RIGHT_TOP: + case OrientationTypes.LEFT_BOTTOM: + return height / width; + } +}; + export const formatFocalLength = (focalLength?: number) => focalLength ? `${focalLength}mm` : undefined; diff --git a/src/utility/size.ts b/src/utility/size.ts new file mode 100644 index 00000000..53fa0c94 --- /dev/null +++ b/src/utility/size.ts @@ -0,0 +1,29 @@ +const DEFAULT_ASPECT_RATIO = 3.0 / 2.0; + +export const getDimensionsFromSize = ( + size: number, + aspectRatioRaw?: string | number, +): { + width: number + height: number + aspectRatio: number +} => { + const aspectRatio = typeof aspectRatioRaw === 'string' + ? parseFloat(aspectRatioRaw) + : aspectRatioRaw || DEFAULT_ASPECT_RATIO; + + let width = size; + let height = size; + + if (aspectRatio > 1) { + height = size / aspectRatio; + } else if (aspectRatio < 1) { + width = size * aspectRatio; + } + + return { + width: Math.round(width), + height: Math.round(height), + aspectRatio, + }; +};