Refactor aspect ratio handling

This commit is contained in:
Sam Becker 2023-11-27 18:33:50 -06:00
parent d9c6b8107e
commit f68430ff74
13 changed files with 128 additions and 68 deletions

View File

@ -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(
<FilmSimulationImageResponse {...{

View File

@ -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_OG,
} from '@/photo/image-response';
import HomeImageResponse from '@/photo/image-response/HomeImageResponse';
@ -21,7 +21,7 @@ export async function GET() {
getIBMPlexMonoMedium(),
]);
const { width, height } = IMAGE_OG_SMALL_SIZE;
const { width, height } = IMAGE_OG_DIMENSION_SMALL;
return new ImageResponse(
<HomeImageResponse {...{ photos, width, height, fontFamily }}/>,

View File

@ -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(
<PhotoImageResponse {...{ photo, width, height, fontFamily }} />,

View File

@ -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(
<CameraImageResponse {...{

View File

@ -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 TagImageResponse from '@/photo/image-response/TagImageResponse';
@ -26,7 +26,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(
<TagImageResponse {...{

View File

@ -1,7 +1,7 @@
import { auth } from '@/auth';
import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache';
import {
IMAGE_OG_SIZE,
IMAGE_OG_DIMENSION,
MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT,
} from '@/photo/image-response';
import TemplateImageResponse from
@ -25,7 +25,7 @@ export async function GET() {
getImageCacheHeadersForAuth(await auth()),
]);
const { width, height } = IMAGE_OG_SIZE;
const { width, height } = IMAGE_OG_DIMENSION;
return new ImageResponse(
(

View File

@ -1,7 +1,7 @@
import { auth } from '@/auth';
import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache';
import {
GRID_OG_SIZE,
GRID_OG_DIMENSION,
MAX_PHOTOS_TO_SHOW_TEMPLATE,
} from '@/photo/image-response';
import TemplateImageResponse from
@ -22,7 +22,7 @@ export async function GET() {
getImageCacheHeadersForAuth(await auth()),
]);
const { width, height } = GRID_OG_SIZE;
const { width, height } = GRID_OG_DIMENSION;
return new ImageResponse(
(

View File

@ -5,7 +5,7 @@ import { cc } from '@/utility/css';
import Link from 'next/link';
import { BiError } from 'react-icons/bi';
import Spinner from '@/components/Spinner';
import { IMAGE_OG_SIZE } from '../photo/image-response';
import { IMAGE_OG_DIMENSION } from '../photo/image-response';
export type OGLoadingState = 'unloaded' | 'loading' | 'loaded' | 'failed';
@ -44,7 +44,7 @@ export default function OGTile({
}
}, [loadingStateExternal, loadingStateInternal]);
const { width, height, ratio } = IMAGE_OG_SIZE;
const { width, height, aspectRatio } = IMAGE_OG_DIMENSION;
return (
<Link
@ -59,7 +59,7 @@ export default function OGTile({
>
<div
className="relative"
style={{ aspectRatio: ratio }}
style={{ aspectRatio }}
>
{loadingState === 'loading' &&
<div className={cc(

View File

@ -18,9 +18,9 @@ import {
generateLocalPostgresString,
} from '@/utility/date';
import { toastSuccess, toastWarning } from '@/toast';
import { getDimensionsFromSize } from '@/utility/size';
const THUMBNAIL_WIDTH = 300;
const THUMBNAIL_HEIGHT = 200;
const THUMBNAIL_SIZE = 300;
export default function PhotoForm({
initialPhotoForm,
@ -67,6 +67,11 @@ export default function PhotoForm({
}
}, [updatedExifData]);
const {
width,
height,
} = getDimensionsFromSize(THUMBNAIL_SIZE, formData.aspectRatio);
// Generate local date strings when
// none can be harvested from EXIF
useEffect(() => {
@ -99,29 +104,36 @@ export default function PhotoForm({
return (
<div className="space-y-8 max-w-[38rem]">
<NextImage
alt="Upload"
src={url}
className={cc(
'border rounded-md overflow-hidden',
'border-gray-200 dark:border-gray-700'
)}
width={THUMBNAIL_WIDTH}
height={THUMBNAIL_HEIGHT}
priority
/>
<CanvasBlurCapture
imageUrl={url}
width={THUMBNAIL_WIDTH}
height={THUMBNAIL_HEIGHT}
onCapture={updateBlurData}
/>
{debugBlur && formData.blurData &&
<img
alt="blur"
src={formData.blurData}
width={1000}
/>}
<div className="flex gap-2">
<NextImage
alt="Upload"
src={url}
className={cc(
'border rounded-md overflow-hidden',
'border-gray-200 dark:border-gray-700'
)}
width={width}
height={height}
priority
/>
<CanvasBlurCapture
imageUrl={url}
width={width}
height={height}
onCapture={updateBlurData}
/>
{debugBlur && formData.blurData &&
<img
alt="blur"
src={formData.blurData}
className={cc(
'border rounded-md overflow-hidden',
'border-gray-200 dark:border-gray-700'
)}
width={width}
height={height}
/>}
</div>
<form
action={type === 'create' ? createPhotoAction : updatePhotoAction}
className="space-y-6 pb-12"

View File

@ -4,7 +4,7 @@ import {
convertTimestampToNaivePostgresString,
convertTimestampWithOffsetToPostgresString,
} from '@/utility/date';
import { getOffsetFromExif } from '@/utility/exif';
import { getAspectRatioFromExif, getOffsetFromExif } from '@/utility/exif';
import { toFixedNumber } from '@/utility/number';
import { convertStringToArray } from '@/utility/string';
import { generateNanoid } from '@/utility/nanoid';
@ -97,10 +97,7 @@ export const convertExifToFormData = (
data: ExifData,
filmSimulation?: FilmSimulation,
): Record<keyof PhotoExif, string | undefined> => ({
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(),

View File

@ -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;

View File

@ -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;

29
src/utility/size.ts Normal file
View File

@ -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,
};
};