asciify-engine 1.0.68 → 1.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1160,6 +1160,25 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
1160
1160
  }
1161
1161
 
1162
1162
  // src/core/simple-api.ts
1163
+ function getSourceDims(el) {
1164
+ if (el instanceof HTMLVideoElement) return { w: el.videoWidth, h: el.videoHeight };
1165
+ if (el instanceof HTMLImageElement) return { w: el.naturalWidth || el.width, h: el.naturalHeight || el.height };
1166
+ return { w: el.width, h: el.height };
1167
+ }
1168
+ function makeHiResCanvas(srcW, srcH, displayW, displayH) {
1169
+ if (srcW <= displayW && srcH <= displayH) return null;
1170
+ const MAX = 2048;
1171
+ const scale = Math.min(1, MAX / Math.max(srcW, srcH));
1172
+ const offW = Math.round(srcW * scale);
1173
+ const offH = Math.round(srcH * scale);
1174
+ if (offW <= displayW && offH <= displayH) return null;
1175
+ const offCanvas = document.createElement("canvas");
1176
+ offCanvas.width = offW;
1177
+ offCanvas.height = offH;
1178
+ const offCtx = offCanvas.getContext("2d");
1179
+ if (!offCtx) return null;
1180
+ return { offCanvas, offCtx, offW, offH };
1181
+ }
1163
1182
  function sizeCanvasToContainer(canvas, container, aspect, srcW, srcH) {
1164
1183
  const { width, height } = container.getBoundingClientRect();
1165
1184
  if (!width || !height) return;
@@ -1186,7 +1205,7 @@ function sizeCanvasToContainer(canvas, container, aspect, srcW, srcH) {
1186
1205
  canvas.style.width = cssW + "px";
1187
1206
  canvas.style.height = cssH + "px";
1188
1207
  }
1189
- async function asciify(source, canvas, { fontSize = 10, artStyle = "classic", options = {} } = {}) {
1208
+ async function asciify(source, canvas, { fontSize, artStyle = "classic", options = {} } = {}) {
1190
1209
  let el;
1191
1210
  if (typeof source === "string") {
1192
1211
  const img = new Image();
@@ -1207,15 +1226,26 @@ async function asciify(source, canvas, { fontSize = 10, artStyle = "classic", op
1207
1226
  el = source;
1208
1227
  }
1209
1228
  const preset = ART_STYLE_PRESETS[artStyle];
1210
- const merged = { ...DEFAULT_OPTIONS, ...preset, ...options, fontSize };
1229
+ const resolvedFontSize = fontSize ?? options.fontSize ?? 10;
1230
+ const merged = { ...DEFAULT_OPTIONS, ...preset, ...options, fontSize: resolvedFontSize };
1211
1231
  const ctx = canvas.getContext("2d");
1212
1232
  if (!ctx) throw new Error("Could not get 2d context from canvas");
1213
- const { frame } = imageToAsciiFrame(el, merged, canvas.width, canvas.height);
1214
- renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height);
1233
+ const { w: srcW, h: srcH } = getSourceDims(el);
1234
+ const hires = makeHiResCanvas(srcW, srcH, canvas.width, canvas.height);
1235
+ if (hires) {
1236
+ const { frame } = imageToAsciiFrame(el, merged, hires.offW, hires.offH);
1237
+ renderFrameToCanvas(hires.offCtx, frame, merged, hires.offW, hires.offH);
1238
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1239
+ ctx.drawImage(hires.offCanvas, 0, 0, canvas.width, canvas.height);
1240
+ } else {
1241
+ const { frame } = imageToAsciiFrame(el, merged, canvas.width, canvas.height);
1242
+ renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height);
1243
+ }
1215
1244
  }
1216
- async function asciifyGif(source, canvas, { fontSize = 10, artStyle = "classic", options = {} } = {}) {
1245
+ async function asciifyGif(source, canvas, { fontSize, artStyle = "classic", options = {} } = {}) {
1217
1246
  const buffer = typeof source === "string" ? await fetch(source).then((r) => r.arrayBuffer()) : source;
1218
- const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
1247
+ const resolvedFontSize = fontSize ?? options.fontSize ?? 10;
1248
+ const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize: resolvedFontSize };
1219
1249
  const ctx = canvas.getContext("2d");
1220
1250
  if (!ctx) throw new Error("Could not get 2d context from canvas");
1221
1251
  const { frames, fps } = await gifToAsciiFrames(buffer, merged, canvas.width, canvas.height);
@@ -1239,10 +1269,11 @@ async function asciifyGif(source, canvas, { fontSize = 10, artStyle = "classic",
1239
1269
  cancelAnimationFrame(animId);
1240
1270
  };
1241
1271
  }
1242
- async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic", options = {}, fitTo, preExtract = false, trim, onReady, onFrame } = {}) {
1272
+ async function asciifyVideo(source, canvas, { fontSize, artStyle = "classic", options = {}, fitTo, preExtract = false, trim, onReady, onFrame } = {}) {
1243
1273
  const trimStart = trim?.start ?? 0;
1244
1274
  const trimEnd = trim?.end;
1245
- const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
1275
+ const resolvedFontSize = fontSize ?? options.fontSize ?? 10;
1276
+ const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize: resolvedFontSize };
1246
1277
  const ctx = canvas.getContext("2d");
1247
1278
  if (!ctx) throw new Error("asciifyVideo: could not get 2d context from canvas.");
1248
1279
  const container = typeof fitTo === "string" ? document.querySelector(fitTo) : fitTo instanceof HTMLElement ? fitTo : null;
@@ -1262,15 +1293,24 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
1262
1293
  video2 = source;
1263
1294
  }
1264
1295
  if (container) sizeCanvasToContainer(canvas, container, video2.videoWidth / video2.videoHeight, video2.videoWidth, video2.videoHeight);
1296
+ const hires2 = makeHiResCanvas(video2.videoWidth, video2.videoHeight, canvas.width, canvas.height);
1297
+ const renderW = hires2 ? hires2.offW : canvas.width;
1298
+ const renderH = hires2 ? hires2.offH : canvas.height;
1265
1299
  const maxDur = trimEnd !== void 0 ? trimEnd - trimStart : 10;
1266
- const { frames, fps } = await videoToAsciiFrames(video2, merged, canvas.width, canvas.height, void 0, maxDur, void 0, trimStart);
1300
+ const { frames, fps } = await videoToAsciiFrames(video2, merged, renderW, renderH, void 0, maxDur, void 0, trimStart);
1267
1301
  let cancelled2 = false, animId2, i = 0, last = performance.now();
1268
1302
  let firstFrame2 = true;
1269
1303
  const interval = 1e3 / fps;
1270
1304
  const tick2 = (now) => {
1271
1305
  if (cancelled2) return;
1272
1306
  if (now - last >= interval) {
1273
- renderFrameToCanvas(ctx, frames[i], merged, canvas.width, canvas.height);
1307
+ if (hires2) {
1308
+ renderFrameToCanvas(hires2.offCtx, frames[i], merged, hires2.offW, hires2.offH);
1309
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1310
+ ctx.drawImage(hires2.offCanvas, 0, 0, canvas.width, canvas.height);
1311
+ } else {
1312
+ renderFrameToCanvas(ctx, frames[i], merged, canvas.width, canvas.height);
1313
+ }
1274
1314
  i = (i + 1) % frames.length;
1275
1315
  last = now;
1276
1316
  if (firstFrame2) {
@@ -1348,6 +1388,7 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
1348
1388
  ro = new ResizeObserver(() => sizeCanvasToContainer(canvas, container, aspect, vw, vh));
1349
1389
  ro.observe(container);
1350
1390
  }
1391
+ const hires = makeHiResCanvas(video.videoWidth, video.videoHeight, canvas.width, canvas.height);
1351
1392
  let cancelled = false;
1352
1393
  let animId;
1353
1394
  let firstFrame = true;
@@ -1357,14 +1398,28 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
1357
1398
  if (video.readyState < 2 || canvas.width === 0 || canvas.height === 0) return;
1358
1399
  if (trimStart > 0 && video.currentTime < trimStart) return;
1359
1400
  if (trimEnd !== void 0 && video.currentTime >= trimEnd) return;
1360
- const { frame } = imageToAsciiFrame(video, merged, canvas.width, canvas.height);
1361
- if (frame.length > 0) {
1362
- renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height, 0, null);
1363
- if (firstFrame) {
1364
- firstFrame = false;
1365
- onReady?.(video);
1401
+ if (hires) {
1402
+ const { frame } = imageToAsciiFrame(video, merged, hires.offW, hires.offH);
1403
+ if (frame.length > 0) {
1404
+ renderFrameToCanvas(hires.offCtx, frame, merged, hires.offW, hires.offH, 0, null);
1405
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1406
+ ctx.drawImage(hires.offCanvas, 0, 0, canvas.width, canvas.height);
1407
+ if (firstFrame) {
1408
+ firstFrame = false;
1409
+ onReady?.(video);
1410
+ }
1411
+ onFrame?.();
1412
+ }
1413
+ } else {
1414
+ const { frame } = imageToAsciiFrame(video, merged, canvas.width, canvas.height);
1415
+ if (frame.length > 0) {
1416
+ renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height, 0, null);
1417
+ if (firstFrame) {
1418
+ firstFrame = false;
1419
+ onReady?.(video);
1420
+ }
1421
+ onFrame?.();
1366
1422
  }
1367
- onFrame?.();
1368
1423
  }
1369
1424
  };
1370
1425
  animId = requestAnimationFrame(tick);