Refine recipe og image

This commit is contained in:
Sam Becker 2025-03-10 09:15:56 -05:00
parent 80dfad2fe1
commit 1697a83b95
13 changed files with 59 additions and 37 deletions

View File

@ -6,7 +6,7 @@ import {
import FilmSimulationImageResponse from import FilmSimulationImageResponse from
'@/image-response/FilmSimulationImageResponse'; '@/image-response/FilmSimulationImageResponse';
import { FilmSimulation } from '@/simulation'; import { FilmSimulation } from '@/simulation';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db';
@ -40,7 +40,7 @@ export async function GET(
headers, headers,
] = await Promise.all([ ] = await Promise.all([
getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, simulation }), getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, simulation }),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -3,7 +3,7 @@ import {
IMAGE_OG_DIMENSION_SMALL, IMAGE_OG_DIMENSION_SMALL,
MAX_PHOTOS_TO_SHOW_PER_CATEGORY, MAX_PHOTOS_TO_SHOW_PER_CATEGORY,
} from '@/image-response'; } from '@/image-response';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import FocalLengthImageResponse from import FocalLengthImageResponse from
@ -42,7 +42,7 @@ export async function GET(
headers, headers,
] = await Promise.all([ ] = await Promise.all([
getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, focal }), getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, focal }),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -4,7 +4,7 @@ import {
MAX_PHOTOS_TO_SHOW_OG, MAX_PHOTOS_TO_SHOW_OG,
} from '@/image-response'; } from '@/image-response';
import HomeImageResponse from '@/image-response/HomeImageResponse'; import HomeImageResponse from '@/image-response/HomeImageResponse';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { isNextImageReadyBasedOnPhotos } from '@/photo'; import { isNextImageReadyBasedOnPhotos } from '@/photo';
@ -19,7 +19,7 @@ export async function GET() {
] = await Promise.all([ ] = await Promise.all([
getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_OG }).catch(() => []), getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_OG }).catch(() => []),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
getIBMPlexMonoMedium(), getIBMPlexMono(),
]); ]);
const { width, height } = IMAGE_OG_DIMENSION_SMALL; const { width, height } = IMAGE_OG_DIMENSION_SMALL;

View File

@ -1,7 +1,7 @@
import { getPhotoCached } from '@/photo/cache'; import { getPhotoCached } from '@/photo/cache';
import { IMAGE_OG_DIMENSION } from '@/image-response'; import { IMAGE_OG_DIMENSION } from '@/image-response';
import PhotoImageResponse from '@/image-response/PhotoImageResponse'; import PhotoImageResponse from '@/image-response/PhotoImageResponse';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { import {
@ -34,7 +34,7 @@ export async function GET(
headers, headers,
] = await Promise.all([ ] = await Promise.all([
getPhotoCached(photoId), getPhotoCached(photoId),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -3,7 +3,7 @@ import {
IMAGE_OG_DIMENSION_SMALL, IMAGE_OG_DIMENSION_SMALL,
MAX_PHOTOS_TO_SHOW_PER_CATEGORY, MAX_PHOTOS_TO_SHOW_PER_CATEGORY,
} from '@/image-response'; } from '@/image-response';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db';
@ -38,7 +38,7 @@ export async function GET(
headers, headers,
] = await Promise.all([ ] = await Promise.all([
getPhotosCached({ recipe, limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY }), getPhotosCached({ recipe, limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY }),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -5,7 +5,7 @@ import {
MAX_PHOTOS_TO_SHOW_PER_CATEGORY, MAX_PHOTOS_TO_SHOW_PER_CATEGORY,
} from '@/image-response'; } from '@/image-response';
import CameraImageResponse from '@/image-response/CameraImageResponse'; import CameraImageResponse from '@/image-response/CameraImageResponse';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db';
@ -42,7 +42,7 @@ export async function GET(
limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY,
camera: camera, camera: camera,
}), }),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -4,7 +4,7 @@ import {
MAX_PHOTOS_TO_SHOW_PER_CATEGORY, MAX_PHOTOS_TO_SHOW_PER_CATEGORY,
} from '@/image-response'; } from '@/image-response';
import TagImageResponse from '@/image-response/TagImageResponse'; import TagImageResponse from '@/image-response/TagImageResponse';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db';
@ -38,7 +38,7 @@ export async function GET(
headers, headers,
] = await Promise.all([ ] = await Promise.all([
getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, tag }), getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, tag }),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -5,7 +5,7 @@ import {
} from '@/image-response'; } from '@/image-response';
import TemplateImageResponse from import TemplateImageResponse from
'@/image-response/TemplateImageResponse'; '@/image-response/TemplateImageResponse';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { isNextImageReadyBasedOnPhotos } from '@/photo'; import { isNextImageReadyBasedOnPhotos } from '@/photo';
@ -20,7 +20,7 @@ export async function GET() {
sortBy: 'priority', sortBy: 'priority',
limit: MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT, limit: MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT,
}).catch(() => []), }).catch(() => []),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -5,7 +5,7 @@ import {
} from '@/image-response'; } from '@/image-response';
import TemplateImageResponse from import TemplateImageResponse from
'@/image-response/TemplateImageResponse'; '@/image-response/TemplateImageResponse';
import { getIBMPlexMonoMedium } from '@/app/font'; import { getIBMPlexMono } from '@/app/font';
import { ImageResponse } from 'next/og'; import { ImageResponse } from 'next/og';
import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache';
import { isNextImageReadyBasedOnPhotos } from '@/photo'; import { isNextImageReadyBasedOnPhotos } from '@/photo';
@ -20,7 +20,7 @@ export async function GET() {
sortBy: 'priority', sortBy: 'priority',
limit: MAX_PHOTOS_TO_SHOW_TEMPLATE, limit: MAX_PHOTOS_TO_SHOW_TEMPLATE,
}).catch(() => []), }).catch(() => []),
getIBMPlexMonoMedium(), getIBMPlexMono(),
getImageResponseCacheControlHeaders(), getImageResponseCacheControlHeaders(),
]); ]);

View File

@ -3,18 +3,31 @@ import path from 'path';
import { cwd } from 'process'; import { cwd } from 'process';
const FONT_IBM_PLEX_MONO_FAMILY = 'IBMPlexMono'; const FONT_IBM_PLEX_MONO_FAMILY = 'IBMPlexMono';
const FONT_IBM_PLEX_MONO_PATH = '/public/fonts/IBMPlexMono-Medium.ttf';
const getFontData = async () => const FONT_IBM_PLEX_MONO_PATH_REGULAR = '/public/fonts/IBMPlexMono-Regular.ttf';
fs.readFileSync(path.join(cwd(), FONT_IBM_PLEX_MONO_PATH)); const FONT_IBM_PLEX_MONO_PATH_MEDIUM = '/public/fonts/IBMPlexMono-Medium.ttf';
export const getIBMPlexMonoMedium = () => getFontData() const getFontData = async (fontPath: string) =>
.then(data => ({ fs.readFileSync(path.join(cwd(), fontPath));
export const getIBMPlexMono = async () => {
const [regular, medium] = await Promise.all([
getFontData(FONT_IBM_PLEX_MONO_PATH_REGULAR),
getFontData(FONT_IBM_PLEX_MONO_PATH_MEDIUM),
]);
return {
fontFamily: FONT_IBM_PLEX_MONO_FAMILY, fontFamily: FONT_IBM_PLEX_MONO_FAMILY,
fonts: [{ fonts: [{
name: FONT_IBM_PLEX_MONO_FAMILY, name: FONT_IBM_PLEX_MONO_FAMILY,
data, data: regular,
weight: 400,
style: 'normal',
} as const, {
name: FONT_IBM_PLEX_MONO_FAMILY,
data: medium,
weight: 500, weight: 500,
style: 'normal', style: 'normal',
} as const], } as const,
})); ],
};
};

View File

@ -15,12 +15,14 @@ export default function RecipeImageResponse({
width, width,
height, height,
fontFamily, fontFamily,
smallText = true,
}: { }: {
recipe: string, recipe: string,
photos: Photo[] photos: Photo[]
width: NextImageSize width: NextImageSize
height: number height: number
fontFamily: string fontFamily: string
smallText?: boolean
}) { }) {
const photo = getPhotoWithRecipeFromPhotos(photos); const photo = getPhotoWithRecipeFromPhotos(photos);
@ -51,7 +53,7 @@ export default function RecipeImageResponse({
tw="flex absolute inset-0" tw="flex absolute inset-0"
style={{ style={{
background: background:
'linear-gradient(to right, rgba(0, 0, 0, .5) 40%, transparent 75%)', 'linear-gradient(to right, rgba(0, 0, 0, .5) 30%, transparent 60%)',
}} }}
/> />
<ImageCaption {...{ <ImageCaption {...{
@ -71,12 +73,19 @@ export default function RecipeImageResponse({
}}> }}>
{photo?.recipeData && {photo?.recipeData &&
<div <div
tw="opacity-60" // tw="opacity-70"
style={{ style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
...smallText ? {
paddingTop: height * .03, paddingTop: height * .03,
lineHeight: 1.22, lineHeight: 1.45,
letterSpacing: '0.03em',
fontSize: height * .06,
} : {
paddingTop: height * .02,
opacity: 0.7,
},
}} }}
> >
{recipeLines.map(text => ( {recipeLines.map(text => (

View File

@ -33,6 +33,7 @@ export default function ImageCaption({
color: 'white', color: 'white',
backgroundBlendMode: 'multiply', backgroundBlendMode: 'multiply',
fontFamily, fontFamily,
fontWeight: 500,
fontSize: height *.08, fontSize: height *.08,
gap, gap,
lineHeight: 1.2, lineHeight: 1.2,

View File

@ -59,19 +59,18 @@ export const generateRecipeText = ({
simulation, simulation,
}: RecipeProps) => { }: RecipeProps) => {
const lines = [ const lines = [
`${labelForFilmSimulation(simulation).large.toLocaleUpperCase()}`, `${labelForFilmSimulation(simulation).small.toLocaleUpperCase()}`,
`DR${recipe.dynamicRange.development} NR${formatNoiseReduction(recipe)}`, `DR${recipe.dynamicRange.development} NR${formatNoiseReduction(recipe)}`,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
`${formatWhiteBalance(recipe).toLocaleUpperCase()} ${formatWhiteBalanceColor(recipe)}`, `${formatWhiteBalance(recipe).toLocaleUpperCase()} ${formatWhiteBalanceColor(recipe)}`,
]; ];
if (recipe.highlight || recipe.shadow) { if (recipe.highlight || recipe.shadow) {
// eslint-disable-next-line max-len lines.push(`HI/SH ${addSign(recipe.highlight)}/${addSign(recipe.shadow)}`);
lines.push(`HIGH/SHAD ${addSign(recipe.highlight)}/${addSign(recipe.shadow)}`);
} }
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
lines.push(`COL${addSign(recipe.color)} SHARP${addSign(recipe.sharpness)} CLAR${addSign(recipe.clarity)}`); lines.push(`CO${addSign(recipe.color)} SH${addSign(recipe.sharpness)} CL${addSign(recipe.clarity)}`);
if (recipe.colorChromeEffect) { if (recipe.colorChromeEffect) {
lines.push(`CHROME ${recipe.colorChromeEffect.toLocaleUpperCase()}`); lines.push(`CHROME ${recipe.colorChromeEffect.toLocaleUpperCase()}`);
@ -141,15 +140,15 @@ export const formatWhiteBalanceColor = ({
whiteBalance: { red, blue }, whiteBalance: { red, blue },
}: FujifilmRecipe) => }: FujifilmRecipe) =>
(red || blue) (red || blue)
? `(R${addSign(red)}/B${addSign(blue)})` ? `R${addSign(red)}/B${addSign(blue)}`
: ''; : '';
export const formatGrain = ({ grainEffect }: FujifilmRecipe) => export const formatGrain = ({ grainEffect }: FujifilmRecipe) =>
grainEffect.roughness === 'off' grainEffect.roughness === 'off'
? 'OFF' ? 'OFF'
: grainEffect.roughness === 'weak' : grainEffect.roughness === 'weak'
? `WEAK/${grainEffect.size.toLocaleUpperCase()}` ? `WEAK/${grainEffect.size === 'small' ? 'SM' : 'LG'}`
: `STRONG/${grainEffect.size.toLocaleUpperCase()}`; : `STRONG/${grainEffect.size === 'small' ? 'SM' : 'LG'}`;
export const formatNoiseReduction = ({ export const formatNoiseReduction = ({
highISONoiseReduction, highISONoiseReduction,