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