osolmaz HF Staff commited on
Commit
7a74796
·
verified ·
1 Parent(s): 6e309e5

perf: cache emoji artwork during rendering

Browse files
Files changed (1) hide show
  1. src/render.ts +33 -10
src/render.ts CHANGED
@@ -4,6 +4,7 @@ type RenderContext = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2
4
  export type WallpaperSymbol = CanvasImageSource | string;
5
 
6
  let noiseTile: HTMLCanvasElement | undefined;
 
7
 
8
  function getNoiseTile(): HTMLCanvasElement {
9
  if (noiseTile) return noiseTile;
@@ -52,6 +53,32 @@ function drawBackground(context: RenderContext, width: number, height: number):
52
  }
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  export function renderWallpaper(
56
  context: RenderContext,
57
  width: number,
@@ -66,6 +93,11 @@ export function renderWallpaper(
66
 
67
  const unit = Math.min(width, height);
68
  const drawSoftShadows = highQuality || placements.length / PLACEMENT_STRIDE <= 240;
 
 
 
 
 
69
  for (let offset = 0; offset < placements.length; offset += PLACEMENT_STRIDE) {
70
  const x = (placements[offset] ?? 0) * unit;
71
  const y = (placements[offset + 1] ?? 0) * unit;
@@ -79,16 +111,7 @@ export function renderWallpaper(
79
  context.shadowBlur = Math.max(2, logoWidth * 0.035);
80
  context.shadowOffsetY = Math.max(2, logoWidth * 0.032);
81
  }
82
- if (typeof symbol === "string") {
83
- context.font = `${logoWidth * 0.88}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif`;
84
- context.textAlign = "center";
85
- context.textBaseline = "alphabetic";
86
- const metrics = context.measureText(symbol);
87
- const visualCenter = ((metrics.actualBoundingBoxAscent ?? logoWidth * 0.7) - (metrics.actualBoundingBoxDescent ?? logoWidth * 0.15)) * 0.5;
88
- context.fillText(symbol, 0, visualCenter, logoWidth);
89
- } else {
90
- context.drawImage(symbol, -logoWidth * 0.5, -logoWidth * 0.5, logoWidth, logoWidth);
91
- }
92
  context.restore();
93
  }
94
  }
 
4
  export type WallpaperSymbol = CanvasImageSource | string;
5
 
6
  let noiseTile: HTMLCanvasElement | undefined;
7
+ const emojiTiles = new Map<string, HTMLCanvasElement>();
8
 
9
  function getNoiseTile(): HTMLCanvasElement {
10
  if (noiseTile) return noiseTile;
 
53
  }
54
  }
55
 
56
+ function emojiTile(symbol: string, targetSize: number, highQuality: boolean): HTMLCanvasElement {
57
+ const requestedSize = highQuality ? Math.max(256, Math.min(2048, 2 ** Math.ceil(Math.log2(targetSize * 1.25)))) : 256;
58
+ const key = `${symbol}:${requestedSize}`;
59
+ const cached = emojiTiles.get(key);
60
+ if (cached) return cached;
61
+
62
+ const tile = document.createElement("canvas");
63
+ tile.width = requestedSize;
64
+ tile.height = requestedSize;
65
+ const context = tile.getContext("2d", { alpha: true });
66
+ if (!context) throw new Error("Could not create emoji tile");
67
+ context.font = `${requestedSize * 0.88}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif`;
68
+ context.fillStyle = "#ffffff";
69
+ context.textAlign = "center";
70
+ context.textBaseline = "alphabetic";
71
+ const metrics = context.measureText(symbol);
72
+ const visualCenter = (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) * 0.5;
73
+ context.fillText(symbol, requestedSize * 0.5, requestedSize * 0.5 + visualCenter, requestedSize);
74
+ emojiTiles.set(key, tile);
75
+ if (emojiTiles.size > 4) {
76
+ const oldestKey = emojiTiles.keys().next().value;
77
+ if (oldestKey) emojiTiles.delete(oldestKey);
78
+ }
79
+ return tile;
80
+ }
81
+
82
  export function renderWallpaper(
83
  context: RenderContext,
84
  width: number,
 
93
 
94
  const unit = Math.min(width, height);
95
  const drawSoftShadows = highQuality || placements.length / PLACEMENT_STRIDE <= 240;
96
+ let maxSymbolWidth = 1;
97
+ for (let offset = 2; offset < placements.length; offset += PLACEMENT_STRIDE) {
98
+ maxSymbolWidth = Math.max(maxSymbolWidth, (placements[offset] ?? 0) * unit);
99
+ }
100
+ const artwork = typeof symbol === "string" ? emojiTile(symbol, maxSymbolWidth, highQuality) : symbol;
101
  for (let offset = 0; offset < placements.length; offset += PLACEMENT_STRIDE) {
102
  const x = (placements[offset] ?? 0) * unit;
103
  const y = (placements[offset + 1] ?? 0) * unit;
 
111
  context.shadowBlur = Math.max(2, logoWidth * 0.035);
112
  context.shadowOffsetY = Math.max(2, logoWidth * 0.032);
113
  }
114
+ context.drawImage(artwork, -logoWidth * 0.5, -logoWidth * 0.5, logoWidth, logoWidth);
 
 
 
 
 
 
 
 
 
115
  context.restore();
116
  }
117
  }