Skip to content

Commit 5fc5c04

Browse files
committed
fix(compare-per-dollar): render PNG natively at 2400x1350 for sharp glyphs
The wrapper used `transform: scale(2)` to upsample a 1200x675 layout into the 2400x1350 ImageResponse. Satori rasterizes text glyphs at the source size before applying CSS transforms, so the bitmap got upscaled and the chart came out blurry. Multiplying every pixel constant by R=2 and rendering the wrapper at full size keeps strokes and text as native high-res vectors.
1 parent 2b2081c commit 5fc5c04

1 file changed

Lines changed: 83 additions & 80 deletions

File tree

  • packages/app/src/app/compare-per-dollar/[slug]/performance-per-dollar.png

packages/app/src/app/compare-per-dollar/[slug]/performance-per-dollar.png/route.tsx

Lines changed: 83 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ import {
1414
export const dynamic = 'force-dynamic';
1515
export const runtime = 'nodejs';
1616

17-
const DISPLAY_SIZE = { width: 1200, height: 675 };
18-
const IMAGE_SCALE = 2;
19-
const SIZE = {
20-
width: DISPLAY_SIZE.width * IMAGE_SCALE,
21-
height: DISPLAY_SIZE.height * IMAGE_SCALE,
22-
};
23-
const CHART_FRAME = { left: 0, top: 18, width: 746, height: 382 };
24-
const CHART = { left: 96, top: 42, width: 630, height: 260 };
17+
// Render natively at high-DPI. CSS `transform: scale()` causes Satori to rasterize
18+
// SVG/text at the source size and bitmap-upsample, which produces a blurry chart.
19+
// Multiplying every pixel value by R keeps glyphs and strokes as vectors at full res.
20+
const R = 2;
21+
const SIZE = { width: 1200 * R, height: 675 * R };
22+
const CHART_FRAME = { left: 0, top: 18 * R, width: 746 * R, height: 382 * R };
23+
const CHART = { left: 96 * R, top: 42 * R, width: 630 * R, height: 260 * R };
2524
const COLORS = {
2625
background: '#0d1117',
2726
panel: '#121a23',
@@ -172,6 +171,8 @@ export async function GET(
172171
.map((row) => ({ x: scaleX(row.target), y: scaleY(row.b!.cost) }));
173172
const workload = [sequence, precision?.toUpperCase()].filter(Boolean).join(' / ');
174173
const showRangeEndpoints = hasLeftExtension || hasRightExtension;
174+
const svgWidth = 760 * R;
175+
const svgHeight = 406 * R;
175176

176177
function renderSeriesPath(points: Point[], stroke: string, dashed: boolean) {
177178
if (points.length < 2) return null;
@@ -180,9 +181,9 @@ export async function GET(
180181
d={pointsPath(points)}
181182
fill="none"
182183
stroke={stroke}
183-
strokeWidth="9"
184+
strokeWidth={9 * R}
184185
strokeOpacity={dashed ? 0.55 : 1}
185-
strokeDasharray={dashed ? '14 10' : undefined}
186+
strokeDasharray={dashed ? `${14 * R} ${10 * R}` : undefined}
186187
strokeLinejoin="round"
187188
strokeLinecap="round"
188189
/>
@@ -194,22 +195,20 @@ export async function GET(
194195
style={{
195196
display: 'flex',
196197
flexDirection: 'column',
197-
width: DISPLAY_SIZE.width,
198-
height: DISPLAY_SIZE.height,
199-
padding: '38px 46px 26px',
198+
width: SIZE.width,
199+
height: SIZE.height,
200+
padding: `${38 * R}px ${46 * R}px ${26 * R}px`,
200201
background: COLORS.background,
201202
color: COLORS.text,
202203
fontFamily: 'Arial, sans-serif',
203-
transform: `scale(${IMAGE_SCALE})`,
204-
transformOrigin: 'top left',
205204
}}
206205
>
207206
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
208-
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
207+
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 * R }}>
209208
<div
210209
style={{
211210
display: 'flex',
212-
fontSize: 19,
211+
fontSize: 19 * R,
213212
fontWeight: 700,
214213
letterSpacing: '0.13em',
215214
textTransform: 'uppercase',
@@ -218,8 +217,10 @@ export async function GET(
218217
>
219218
InferenceX Performance per Dollar
220219
</div>
221-
<div style={{ display: 'flex', fontSize: 41, fontWeight: 800 }}>{parsed.model.label}</div>
222-
<div style={{ display: 'flex', fontSize: 25, color: COLORS.muted }}>
220+
<div style={{ display: 'flex', fontSize: 41 * R, fontWeight: 800 }}>
221+
{parsed.model.label}
222+
</div>
223+
<div style={{ display: 'flex', fontSize: 25 * R, color: COLORS.muted }}>
223224
{aLabel} vs {bLabel} | Cost per Million Tokens
224225
</div>
225226
</div>
@@ -228,37 +229,39 @@ export async function GET(
228229
display: 'flex',
229230
flexDirection: 'column',
230231
alignItems: 'flex-end',
231-
border: `1px solid ${COLORS.border}`,
232-
borderRadius: 12,
233-
padding: '13px 17px',
232+
border: `${R}px solid ${COLORS.border}`,
233+
borderRadius: 12 * R,
234+
padding: `${13 * R}px ${17 * R}px`,
234235
background: COLORS.panel,
235-
gap: 5,
236+
gap: 5 * R,
236237
}}
237238
>
238-
<div style={{ display: 'flex', fontSize: 14, color: COLORS.muted }}>DEFAULT WORKLOAD</div>
239-
<div style={{ display: 'flex', fontSize: 21, fontWeight: 700 }}>
239+
<div style={{ display: 'flex', fontSize: 14 * R, color: COLORS.muted }}>
240+
DEFAULT WORKLOAD
241+
</div>
242+
<div style={{ display: 'flex', fontSize: 21 * R, fontWeight: 700 }}>
240243
{workload || 'Default comparison'}
241244
</div>
242-
<div style={{ display: 'flex', fontSize: 14, color: COLORS.muted }}>
245+
<div style={{ display: 'flex', fontSize: 14 * R, color: COLORS.muted }}>
243246
Lower cost is better
244247
</div>
245248
</div>
246249
</div>
247250

248-
<div style={{ display: 'flex', flex: 1, gap: 34, marginTop: 22 }}>
249-
<div style={{ display: 'flex', position: 'relative', width: 760, height: 406 }}>
251+
<div style={{ display: 'flex', flex: 1, gap: 34 * R, marginTop: 22 * R }}>
252+
<div style={{ display: 'flex', position: 'relative', width: svgWidth, height: svgHeight }}>
250253
<svg
251-
width="760"
252-
height="406"
253-
viewBox="0 0 760 406"
254+
width={svgWidth}
255+
height={svgHeight}
256+
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
254257
style={{ position: 'absolute', left: 0, top: 0 }}
255258
>
256259
<rect
257260
x={CHART_FRAME.left}
258261
y={CHART_FRAME.top}
259262
width={CHART_FRAME.width}
260263
height={CHART_FRAME.height}
261-
rx="13"
264+
rx={13 * R}
262265
fill={COLORS.panel}
263266
stroke={COLORS.border}
264267
/>
@@ -272,7 +275,7 @@ export async function GET(
272275
y1={y}
273276
y2={y}
274277
stroke={COLORS.grid}
275-
strokeWidth="2"
278+
strokeWidth={2 * R}
276279
/>
277280
);
278281
})}
@@ -284,9 +287,9 @@ export async function GET(
284287
x1={x}
285288
x2={x}
286289
y1={CHART.top + CHART.height}
287-
y2={CHART.top + CHART.height + 6}
290+
y2={CHART.top + CHART.height + 6 * R}
288291
stroke={COLORS.muted}
289-
strokeWidth="2"
292+
strokeWidth={2 * R}
290293
/>
291294
);
292295
})}
@@ -301,21 +304,21 @@ export async function GET(
301304
key={`a-${index}`}
302305
cx={point.x}
303306
cy={point.y}
304-
r="10"
307+
r={10 * R}
305308
fill={COLORS.a}
306309
stroke={COLORS.background}
307-
strokeWidth="4"
310+
strokeWidth={4 * R}
308311
/>
309312
))}
310313
{bHighlightPoints.map((point, index) => (
311314
<circle
312315
key={`b-${index}`}
313316
cx={point.x}
314317
cy={point.y}
315-
r="10"
318+
r={10 * R}
316319
fill={COLORS.b}
317320
stroke={COLORS.background}
318-
strokeWidth="4"
321+
strokeWidth={4 * R}
319322
/>
320323
))}
321324
</svg>
@@ -325,12 +328,12 @@ export async function GET(
325328
style={{
326329
display: 'flex',
327330
position: 'absolute',
328-
left: CHART_FRAME.left + 14,
329-
top: scaleY(tick) - 9,
330-
width: CHART.left - CHART_FRAME.left - 28,
331+
left: CHART_FRAME.left + 14 * R,
332+
top: scaleY(tick) - 9 * R,
333+
width: CHART.left - CHART_FRAME.left - 28 * R,
331334
justifyContent: 'flex-end',
332335
color: COLORS.muted,
333-
fontSize: 15,
336+
fontSize: 15 * R,
334337
}}
335338
>
336339
{moneyForStep(tick, yStep)}
@@ -342,12 +345,12 @@ export async function GET(
342345
style={{
343346
display: 'flex',
344347
position: 'absolute',
345-
left: scaleX(row.target) - 32,
346-
top: CHART.top + CHART.height + 15,
347-
width: 64,
348+
left: scaleX(row.target) - 32 * R,
349+
top: CHART.top + CHART.height + 15 * R,
350+
width: 64 * R,
348351
justifyContent: 'center',
349352
color: COLORS.muted,
350-
fontSize: 16,
353+
fontSize: 16 * R,
351354
fontWeight: 600,
352355
}}
353356
>
@@ -359,12 +362,12 @@ export async function GET(
359362
style={{
360363
display: 'flex',
361364
position: 'absolute',
362-
left: scaleX(xMin) - 4,
363-
top: CHART.top + CHART.height + 16,
364-
width: 56,
365+
left: scaleX(xMin) - 4 * R,
366+
top: CHART.top + CHART.height + 16 * R,
367+
width: 56 * R,
365368
justifyContent: 'flex-start',
366369
color: COLORS.faint,
367-
fontSize: 13,
370+
fontSize: 13 * R,
368371
fontStyle: 'italic',
369372
}}
370373
>
@@ -376,12 +379,12 @@ export async function GET(
376379
style={{
377380
display: 'flex',
378381
position: 'absolute',
379-
left: scaleX(xMax) - 52,
380-
top: CHART.top + CHART.height + 16,
381-
width: 56,
382+
left: scaleX(xMax) - 52 * R,
383+
top: CHART.top + CHART.height + 16 * R,
384+
width: 56 * R,
382385
justifyContent: 'flex-end',
383386
color: COLORS.faint,
384-
fontSize: 13,
387+
fontSize: 13 * R,
385388
fontStyle: 'italic',
386389
}}
387390
>
@@ -393,11 +396,11 @@ export async function GET(
393396
display: 'flex',
394397
position: 'absolute',
395398
left: CHART.left,
396-
top: CHART.top + CHART.height + 38,
399+
top: CHART.top + CHART.height + 38 * R,
397400
width: CHART.width,
398401
justifyContent: 'center',
399402
color: COLORS.muted,
400-
fontSize: 15,
403+
fontSize: 15 * R,
401404
fontWeight: 600,
402405
}}
403406
>
@@ -409,11 +412,11 @@ export async function GET(
409412
display: 'flex',
410413
position: 'absolute',
411414
left: CHART.left,
412-
top: CHART.top + CHART.height + 62,
415+
top: CHART.top + CHART.height + 62 * R,
413416
width: CHART.width,
414417
justifyContent: 'center',
415418
color: COLORS.faint,
416-
fontSize: 13,
419+
fontSize: 13 * R,
417420
fontStyle: 'italic',
418421
}}
419422
>
@@ -427,33 +430,33 @@ export async function GET(
427430
display: 'flex',
428431
flex: 1,
429432
flexDirection: 'column',
430-
gap: 17,
431-
paddingTop: 18,
433+
gap: 17 * R,
434+
paddingTop: 18 * R,
432435
}}
433436
>
434-
<div style={{ display: 'flex', fontSize: 18, fontWeight: 700 }}>
437+
<div style={{ display: 'flex', fontSize: 18 * R, fontWeight: 700 }}>
435438
Matched Interactivity
436439
</div>
437-
<div style={{ display: 'flex', gap: 20, fontSize: 15, color: COLORS.muted }}>
438-
<span style={{ display: 'flex', gap: 7, alignItems: 'center' }}>
440+
<div style={{ display: 'flex', gap: 20 * R, fontSize: 15 * R, color: COLORS.muted }}>
441+
<span style={{ display: 'flex', gap: 7 * R, alignItems: 'center' }}>
439442
<span
440443
style={{
441444
display: 'flex',
442-
width: 19,
443-
height: 6,
444-
borderRadius: 3,
445+
width: 19 * R,
446+
height: 6 * R,
447+
borderRadius: 3 * R,
445448
background: COLORS.a,
446449
}}
447450
/>
448451
{aLabel}
449452
</span>
450-
<span style={{ display: 'flex', gap: 7, alignItems: 'center' }}>
453+
<span style={{ display: 'flex', gap: 7 * R, alignItems: 'center' }}>
451454
<span
452455
style={{
453456
display: 'flex',
454-
width: 19,
455-
height: 6,
456-
borderRadius: 3,
457+
width: 19 * R,
458+
height: 6 * R,
459+
borderRadius: 3 * R,
457460
background: COLORS.b,
458461
}}
459462
/>
@@ -467,17 +470,17 @@ export async function GET(
467470
style={{
468471
display: 'flex',
469472
flexDirection: 'column',
470-
gap: 6,
471-
border: `1px solid ${COLORS.border}`,
472-
borderRadius: 10,
473-
padding: '11px 13px',
473+
gap: 6 * R,
474+
border: `${R}px solid ${COLORS.border}`,
475+
borderRadius: 10 * R,
476+
padding: `${11 * R}px ${13 * R}px`,
474477
background: COLORS.panel,
475478
}}
476479
>
477-
<div style={{ display: 'flex', color: COLORS.muted, fontSize: 13 }}>
480+
<div style={{ display: 'flex', color: COLORS.muted, fontSize: 13 * R }}>
478481
{row.target} tok/s/user
479482
</div>
480-
<div style={{ display: 'flex', gap: 15, fontSize: 19, fontWeight: 700 }}>
483+
<div style={{ display: 'flex', gap: 15 * R, fontSize: 19 * R, fontWeight: 700 }}>
481484
<span style={{ display: 'flex', color: COLORS.a }}>
482485
{row.a ? money(row.a.cost) : 'N/A'}
483486
</span>
@@ -488,7 +491,7 @@ export async function GET(
488491
</div>
489492
))
490493
) : (
491-
<div style={{ display: 'flex', fontSize: 18, color: COLORS.muted }}>
494+
<div style={{ display: 'flex', fontSize: 18 * R, color: COLORS.muted }}>
492495
No matched cost data available.
493496
</div>
494497
)}
@@ -499,8 +502,8 @@ export async function GET(
499502
style={{
500503
display: 'flex',
501504
justifyContent: 'space-between',
502-
paddingTop: 9,
503-
fontSize: 15,
505+
paddingTop: 9 * R,
506+
fontSize: 15 * R,
504507
color: COLORS.muted,
505508
}}
506509
>

0 commit comments

Comments
 (0)