blecsd-media

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

@blecsd/media Package Skill

@blecsd/media 包技能

The
@blecsd/media
package provides image parsing, rendering, and video playback for blECSd terminal applications. It includes complete GIF and PNG parsers, ANSI rendering to 256-color terminal, animated image widgets, video player integration (mpv/mplayer), and W3M overlay support.
Install:
pnpm add @blecsd/media
Peer dependency:
blecsd >= 0.6.0
@blecsd/media
包为blECSd终端应用提供图像解析、渲染和视频播放功能。它包含完整的GIF和PNG解析器、向256色终端的ANSI渲染、动图组件、视频播放器集成(mpv/mplayer)以及W3M覆盖层支持。
安装:
pnpm add @blecsd/media
对等依赖:
blecsd >= 0.6.0

Subpath Imports

子路径导入

The package exposes focused subpath imports:
typescript
import { parseGIF, frameToRGBA } from '@blecsd/media/gif';
import { parsePNG, extractPixels } from '@blecsd/media/png';
import { renderToAnsi, scaleBitmap, rgbTo256Color } from '@blecsd/media/render';
import { createImage, setImageData, play, pause } from '@blecsd/media/widgets/image';
import { createVideo, detectVideoPlayer } from '@blecsd/media/widgets/video';
import { createW3MOverlay } from '@blecsd/media/overlay';
Or use the namespace API from the main entry:
typescript
import { gif, png, ansiRender, imageWidget, videoWidget, w3m } from '@blecsd/media';
该包提供聚焦的子路径导入:
typescript
import { parseGIF, frameToRGBA } from '@blecsd/media/gif';
import { parsePNG, extractPixels } from '@blecsd/media/png';
import { renderToAnsi, scaleBitmap, rgbTo256Color } from '@blecsd/media/render';
import { createImage, setImageData, play, pause } from '@blecsd/media/widgets/image';
import { createVideo, detectVideoPlayer } from '@blecsd/media/widgets/video';
import { createW3MOverlay } from '@blecsd/media/overlay';
或者从主入口使用命名空间API:
typescript
import { gif, png, ansiRender, imageWidget, videoWidget, w3m } from '@blecsd/media';

GIF Parser

GIF解析器

Complete GIF parser with LZW decompression and animation support.
typescript
import { parseGIF, frameToRGBA, validateGIFSignature } from '@blecsd/media/gif';

// Read file
const buffer = await fs.readFile('animation.gif');

// Validate
if (!validateGIFSignature(buffer)) throw new Error('Not a GIF');

// Parse
const result = parseGIF(buffer);
// result: { header, frames[], globalColorTable }

// Get RGBA pixels for a frame
const rgba = frameToRGBA(result.frames[0]);
// rgba: Uint8Array of [r, g, b, a, r, g, b, a, ...]
Key functions:
  • parseGIF(buffer)
    - Parse full GIF with animation frames
  • parseGIFHeader(buffer)
    - Parse header only
  • validateGIFSignature(buffer)
    - Check GIF87a/GIF89a signature
  • frameToRGBA(frame)
    - Convert frame to RGBA pixel data
  • deinterlace(pixels, width, height)
    - Deinterlace interlaced frames
  • parseColorTable(buffer, offset, size)
    - Parse color table
  • readSubBlocks(buffer, offset)
    - Read GIF sub-blocks
  • decompressLZW(data, minCodeSize)
    - LZW decompression
  • createBitReader(data)
    /
    readCode(reader, codeSize)
    - Bit-level reading
支持LZW解压缩和动画的完整GIF解析器。
typescript
import { parseGIF, frameToRGBA, validateGIFSignature } from '@blecsd/media/gif';

// 读取文件
const buffer = await fs.readFile('animation.gif');

// 验证
if (!validateGIFSignature(buffer)) throw new Error('不是GIF文件');

// 解析
const result = parseGIF(buffer);
// result: { header, frames[], globalColorTable }

// 获取某一帧的RGBA像素数据
const rgba = frameToRGBA(result.frames[0]);
// rgba: Uint8Array 格式为 [r, g, b, a, r, g, b, a, ...]
核心函数:
  • parseGIF(buffer)
    - 解析包含动画帧的完整GIF
  • parseGIFHeader(buffer)
    - 仅解析头部
  • validateGIFSignature(buffer)
    - 检查GIF87a/GIF89a签名
  • frameToRGBA(frame)
    - 将帧转换为RGBA像素数据
  • deinterlace(pixels, width, height)
    - 对隔行扫描的帧进行去隔行处理
  • parseColorTable(buffer, offset, size)
    - 解析颜色表
  • readSubBlocks(buffer, offset)
    - 读取GIF子块
  • decompressLZW(data, minCodeSize)
    - LZW解压缩
  • createBitReader(data)
    /
    readCode(reader, codeSize)
    - 位级读取

PNG Parser

PNG解析器

Complete PNG parser with filter reconstruction.
typescript
import { parsePNG, extractPixels, parseChunks } from '@blecsd/media/png';

const buffer = await fs.readFile('image.png');
const result = parsePNG(buffer);
// result: { header (IHDR), pixels, chunks[] }

// Or parse step by step
const chunks = parseChunks(buffer);
const header = parseIHDR(chunks[0].data);
const pixels = extractPixels(chunks);
Key functions:
  • parsePNG(buffer)
    - Parse full PNG
  • parseChunks(buffer)
    - Parse PNG chunks
  • parseIHDR(data)
    - Parse image header (width, height, bitDepth, colorType)
  • reconstructFilters(scanlines, width, height, bitDepth)
    - Reconstruct PNG filters
  • paethPredictor(a, b, c)
    - PNG Paeth predictor
  • extractPixels(chunks)
    - Extract raw pixel data
  • parsePLTE(buffer)
    - Parse palette chunk
支持过滤器重建的完整PNG解析器。
typescript
import { parsePNG, extractPixels, parseChunks } from '@blecsd/media/png';

const buffer = await fs.readFile('image.png');
const result = parsePNG(buffer);
// result: { header (IHDR), pixels, chunks[] }

// 或者分步解析
const chunks = parseChunks(buffer);
const header = parseIHDR(chunks[0].data);
const pixels = extractPixels(chunks);
核心函数:
  • parsePNG(buffer)
    - 解析完整PNG
  • parseChunks(buffer)
    - 解析PNG块
  • parseIHDR(data)
    - 解析图像头部(宽度、高度、位深度、颜色类型)
  • reconstructFilters(scanlines, width, height, bitDepth)
    - 重建PNG过滤器
  • paethPredictor(a, b, c)
    - PNG Paeth预测器
  • extractPixels(chunks)
    - 提取原始像素数据
  • parsePLTE(buffer)
    - 解析调色板块

ANSI Rendering

ANSI渲染

Convert bitmap data to terminal-renderable ANSI output.
typescript
import { renderToAnsi, scaleBitmap, cellMapToString, rgbTo256Color } from '@blecsd/media/render';

// Render bitmap to ANSI cells
const cellMap = renderToAnsi(bitmap, {
  width: 40,        // Target width in terminal columns
  height: 20,       // Target height in terminal rows
  mode: 'halfblock' // 'halfblock' | 'braille' | 'ascii'
});

// Convert to string for output
const output = cellMapToString(cellMap);

// Scale a bitmap
const scaled = scaleBitmap(bitmap, 40, 20);

// Color conversion
const color256 = rgbTo256Color(255, 128, 0); // RGB to 256-color palette
const lum = rgbLuminance(255, 128, 0);       // Compute luminance
const char = luminanceToChar(lum);            // Map to ASCII char

// Blend colors
const blended = blendWithBackground([255, 128, 0], [0, 0, 0]);
RenderMode options:
  • 'halfblock'
    - Uses half-block characters for 2 pixels per cell vertically
  • 'braille'
    - Uses braille characters for 2x4 pixels per cell (highest density)
  • 'ascii'
    - Uses ASCII characters mapped by luminance
将位图数据转换为可在终端渲染的ANSI输出。
typescript
import { renderToAnsi, scaleBitmap, cellMapToString, rgbTo256Color } from '@blecsd/media/render';

// 将位图渲染为ANSI单元格
const cellMap = renderToAnsi(bitmap, {
  width: 40,        // 终端列数目标宽度
  height: 20,       // 终端行数目标高度
  mode: 'halfblock' // 'halfblock' | 'braille' | 'ascii'
});

// 转换为字符串用于输出
const output = cellMapToString(cellMap);

// 缩放位图
const scaled = scaleBitmap(bitmap, 40, 20);

// 颜色转换
const color256 = rgbTo256Color(255, 128, 0); // RGB转256色调色板
const lum = rgbLuminance(255, 128, 0);       // 计算亮度
const char = luminanceToChar(lum);            // 映射为ASCII字符

// 颜色混合
const blended = blendWithBackground([255, 128, 0], [0, 0, 0]);
渲染模式选项:
  • 'halfblock'
    - 使用半块字符,每个单元格垂直显示2个像素
  • 'braille'
    - 使用盲文字符,每个单元格显示2x4个像素(最高密度)
  • 'ascii'
    - 使用按亮度映射的ASCII字符

Image Widget

图像组件

High-level widget for displaying static and animated images.
typescript
import { createImage, setImageData, play, pause, stop } from '@blecsd/media/widgets/image';

// Create image widget
const eid = createImage(world, {
  position: { x: 0, y: 0 },
  dimensions: { width: 40, height: 20 },
  renderMode: 'halfblock',
});

// Set image data (from parsed GIF/PNG)
setImageData(eid, {
  frames: gifResult.frames.map(f => frameToRGBA(f)),
  width: gifResult.header.width,
  height: gifResult.header.height,
  frameCount: gifResult.frames.length,
  delays: gifResult.frames.map(f => f.delay),
});

// Animation control (for animated GIFs)
play(eid);
pause(eid);
stop(eid);

// Set specific frame
setFrame(eid, 3);

// Get current state
const bitmap = getImageBitmap(eid);
const cellMap = getImageCellMap(eid);

// Aspect ratio helper
const { width, height } = calculateAspectRatioDimensions(
  sourceWidth, sourceHeight,
  targetWidth, targetHeight
);

// Cleanup
clearImageCache(eid);
clearAllImageCaches();

// Type guard
if (isImage(world, eid)) { /* ... */ }
用于显示静态和动态图像的高级组件。
typescript
import { createImage, setImageData, play, pause, stop } from '@blecsd/media/widgets/image';

// 创建图像组件
const eid = createImage(world, {
  position: { x: 0, y: 0 },
  dimensions: { width: 40, height: 20 },
  renderMode: 'halfblock',
});

// 设置图像数据(来自解析后的GIF/PNG)
setImageData(eid, {
  frames: gifResult.frames.map(f => frameToRGBA(f)),
  width: gifResult.header.width,
  height: gifResult.header.height,
  frameCount: gifResult.frames.length,
  delays: gifResult.frames.map(f => f.delay),
});

// 动画控制(针对GIF动图)
play(eid);
pause(eid);
stop(eid);

// 设置特定帧
setFrame(eid, 3);

// 获取当前状态
const bitmap = getImageBitmap(eid);
const cellMap = getImageCellMap(eid);

// 宽高比辅助工具
const { width, height } = calculateAspectRatioDimensions(
  sourceWidth, sourceHeight,
  targetWidth, targetHeight
);

// 清理
clearImageCache(eid);
clearAllImageCaches();

// 类型守卫
if (isImage(world, eid)) { /* ... */ }

Video Widget

视频组件

Video playback using external players (mpv or mplayer).
typescript
import { createVideo, detectVideoPlayer, getVideoPlaybackState } from '@blecsd/media/widgets/video';

// Auto-detect available video player
const player = detectVideoPlayer(); // 'mpv' | 'mplayer' | undefined

// Create video widget
const eid = createVideo(world, {
  position: { x: 0, y: 0 },
  dimensions: { width: 80, height: 24 },
  source: '/path/to/video.mp4',
  player: player,        // 'mpv' | 'mplayer'
  autoplay: true,
});

// Check state
const state = getVideoPlaybackState(eid); // 'stopped' | 'playing' | 'paused'

// Get detected player
const detectedPlayer = getVideoPlayer(eid);

// Build command args (for manual control)
const args = buildMpvArgs({ source: 'video.mp4', width: 80, height: 24 });
const args2 = buildMplayerArgs({ source: 'video.mp4' });
const args3 = buildPlayerArgs({ source: 'video.mp4', player: 'mpv' });

// Send commands to running player
sendPauseCommand(handle);
sendSeekCommand(handle, 10); // Seek 10 seconds

// Type guard
if (isVideo(world, eid)) { /* ... */ }
使用外部播放器(mpv或mplayer)进行视频播放。
typescript
import { createVideo, detectVideoPlayer, getVideoPlaybackState } from '@blecsd/media/widgets/video';

// 自动检测可用的视频播放器
const player = detectVideoPlayer(); // 'mpv' | 'mplayer' | undefined

// 创建视频组件
const eid = createVideo(world, {
  position: { x: 0, y: 0 },
  dimensions: { width: 80, height: 24 },
  source: '/path/to/video.mp4',
  player: player,        // 'mpv' | 'mplayer'
  autoplay: true,
});

// 检查状态
const state = getVideoPlaybackState(eid); // 'stopped' | 'playing' | 'paused'

// 获取检测到的播放器
const detectedPlayer = getVideoPlayer(eid);

// 构建命令参数(用于手动控制)
const args = buildMpvArgs({ source: 'video.mp4', width: 80, height: 24 });
const args2 = buildMplayerArgs({ source: 'video.mp4' });
const args3 = buildPlayerArgs({ source: 'video.mp4', player: 'mpv' });

// 向运行中的播放器发送命令
sendPauseCommand(handle);
sendSeekCommand(handle, 10); // 快进10秒

// 类型守卫
if (isVideo(world, eid)) { /* ... */ }

W3M Overlay

W3M覆盖层

Use W3M's terminal graphics protocol for high-quality image display.
typescript
import { createW3MOverlay } from '@blecsd/media/overlay';

const eid = createW3MOverlay(world, {
  position: { x: 0, y: 0 },
  dimensions: { width: 40, height: 20 },
  imagePath: '/path/to/image.png',
});
使用W3M的终端图形协议实现高质量图像显示。
typescript
import { createW3MOverlay } from '@blecsd/media/overlay';

const eid = createW3MOverlay(world, {
  position: { x: 0, y: 0 },
  dimensions: { width: 40, height: 20 },
  imagePath: '/path/to/image.png',
});

Common Patterns

常见使用模式

Display a GIF in Terminal

在终端中显示GIF

typescript
import { parseGIF, frameToRGBA } from '@blecsd/media/gif';
import { renderToAnsi, cellMapToString, scaleBitmap } from '@blecsd/media/render';

const buffer = await fs.readFile('cat.gif');
const gif = parseGIF(buffer);

for (const frame of gif.frames) {
  const rgba = frameToRGBA(frame);
  const bitmap = { data: rgba, width: gif.header.width, height: gif.header.height };
  const scaled = scaleBitmap(bitmap, 40, 20);
  const cellMap = renderToAnsi(scaled, { mode: 'halfblock' });
  console.log(cellMapToString(cellMap));
}
typescript
import { parseGIF, frameToRGBA } from '@blecsd/media/gif';
import { renderToAnsi, cellMapToString, scaleBitmap } from '@blecsd/media/render';

const buffer = await fs.readFile('cat.gif');
const gif = parseGIF(buffer);

for (const frame of gif.frames) {
  const rgba = frameToRGBA(frame);
  const bitmap = { data: rgba, width: gif.header.width, height: gif.header.height };
  const scaled = scaleBitmap(bitmap, 40, 20);
  const cellMap = renderToAnsi(scaled, { mode: 'halfblock' });
  console.log(cellMapToString(cellMap));
}

Display a PNG

显示PNG

typescript
import { parsePNG } from '@blecsd/media/png';
import { renderToAnsi, cellMapToString } from '@blecsd/media/render';

const buffer = await fs.readFile('photo.png');
const png = parsePNG(buffer);
const cellMap = renderToAnsi(
  { data: png.pixels, width: png.header.width, height: png.header.height },
  { width: 60, height: 30, mode: 'braille' }
);
console.log(cellMapToString(cellMap));
typescript
import { parsePNG } from '@blecsd/media/png';
import { renderToAnsi, cellMapToString } from '@blecsd/media/render';

const buffer = await fs.readFile('photo.png');
const png = parsePNG(buffer);
const cellMap = renderToAnsi(
  { data: png.pixels, width: png.header.width, height: png.header.height },
  { width: 60, height: 30, mode: 'braille' }
);
console.log(cellMapToString(cellMap));

Best Practices

最佳实践

  1. Choose the right render mode.
    braille
    gives highest density (2x4 pixels per cell),
    halfblock
    is the default and works well for most images,
    ascii
    is most compatible.
  2. Scale before rendering. Use
    scaleBitmap
    to fit the image to your terminal dimensions before converting to ANSI.
  3. Handle animated GIFs with the image widget. Don't manually loop frames; the widget handles animation timing.
  4. Video requires external players.
    detectVideoPlayer()
    checks for mpv or mplayer. If neither is found, video won't work.
  5. W3M overlay requires w3mimgdisplay. This is a separate binary from w3m.
  6. Clean up image caches. Call
    clearImageCache(eid)
    or
    clearAllImageCaches()
    to free memory.
  7. Use subpath imports for tree-shaking. Import from
    @blecsd/media/gif
    instead of
    @blecsd/media
    to only pull in what you need.
  1. 选择合适的渲染模式
    braille
    模式提供最高密度(每个单元格2x4像素),
    halfblock
    是默认模式,适用于大多数图像,
    ascii
    模式兼容性最好。
  2. 渲染前先缩放。在转换为ANSI之前,使用
    scaleBitmap
    将图像调整到终端尺寸。
  3. 使用图像组件处理GIF动图。不要手动循环帧,组件会处理动画计时。
  4. 视频播放需要外部播放器
    detectVideoPlayer()
    会检查mpv或mplayer是否存在。如果两者都未找到,视频功能将无法使用。
  5. W3M覆盖层需要w3mimgdisplay。这是w3m的独立二进制文件。
  6. 清理图像缓存。调用
    clearImageCache(eid)
    clearAllImageCaches()
    释放内存。
  7. 使用子路径导入实现树摇优化。从
    @blecsd/media/gif
    导入而非
    @blecsd/media
    ,仅引入所需功能。