组件约 12 分钟
图像与图标
img 接受文件系统路径、data URL 或 http(s) URL;svg 使用 GPUI 的单色图标渲染器。
<img>
<img> 通过 GPUI 的图像元素绘制。它从磁盘、data URL 或 http(s) 加载 PNG、JPEG、WebP、GIF、SVG、BMP、TIFF、ICO 以及 Netpbm。这里的 SVG 是一张全彩图像,而非可染色的图标。
<img
src={fileURLToPath(new URL('./photo.png', import.meta.url))}
objectFit="cover"
style={{ width: 240, height: 140, borderRadius: 12 }}
/>
const src = `data:image/png;base64,${Buffer.from(pngBytes).toString('base64')}`
<img src={src} style={{ width: 240, height: 140 }} />
<img
src="https://example.com/avatar.png"
objectFit="cover"
style={{ width: 48, height: 48, borderRadius: 24 }}
/>
Data URL 支持上面列出的所有图像格式,接受 base64 与百分号编码的载荷。远程 URL 与磁盘文件共用同一套 GPUI 图像缓存,不会被写入临时文件。
objectFit 与 CSS 一致:"contain"(默认)、"cover"、"fill"、"scaleDown" 或 "none"。空的 src 或加载失败会显示一个兜底占位符,而不会崩溃。仍在加载的 URL 会画出一块声明尺寸的空盒子,没有加载转圈。
borderRadius 会裁剪位图,GPUI 按这些圆角绘制图像。父级的 overflow: "hidden" 包裹层不会裁剪 <img> 子元素,请把圆角直接写在图像上。
<img
src={avatarUrl}
objectFit="cover"
style={{ width: 32, height: 32, borderRadius: 16 }}
/>
来自缓冲区的实时图像
Data URL 依然可用,但它会把字节 base64 编码进变更 JSON 里。对于波形、canvas 导出,或任何你已存在于内存中的帧,直接通过 <img> 的 ref 推送原始字节,这个调用会跳过 JSON。
setImage 接受已编码的 PNG、JPEG、WebP、GIF、SVG、BMP、TIFF、ICO 或 Netpbm。setImagePixels 默认接受打包的 RGBA。实时波形优先用像素,没有 PNG 编码,也没有 JSON。
当你的数据源本身就产出 BGRA 时,传入 { format: 'bgra' }。这是 GPUI 的原生顺序,因此上传会跳过一次逐像素的 swizzle。ffmpeg(-pix_fmt bgra)、VideoToolbox 与 node-canvas 的 toBuffer('raw') 都产出它。
img.current?.setImagePixels(width, height, rgba)
img.current?.setImagePixels(width, height, bgra, { format: 'bgra' })
两者都可以在挂载后于 useLayoutEffect 中调用。后续 React src 的变更会覆盖这些像素。Alpha 是直通(straight)的,而非预乘(premultiplied)。
没有 density 参数。setImagePixels 上的 width 和 height 是位图像素,style.width 和 style.height 是布局盒子。在 retina 屏幕上,应按盒子尺寸的 2x(或 devicePixelRatio)上传,否则 GPUI 会把一个逻辑像素拉伸成四个屏幕像素。
import { createCanvas } from 'canvas'
import { useLayoutEffect, useRef } from 'react'
import type { ImgInstance } from '@gpuix/react'
function Waveform({ samples }: { samples: Float32Array }) {
const img = useRef<ImgInstance>(null)
useLayoutEffect(() => {
const width = 1600
const height = 160
const canvas = createCanvas(width, height)
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#1a1a2e'
ctx.fillRect(0, 0, width, height)
ctx.strokeStyle = '#5ca9ff'
ctx.lineWidth = 2
ctx.beginPath()
for (let x = 0; x < samples.length; x++) {
const y = height / 2 - samples[x]! * (height / 2 - 8)
if (x === 0) ctx.moveTo(x, y)
else ctx.lineTo(x, y)
}
ctx.stroke()
img.current?.setImagePixels(width, height, canvas.toBuffer('raw'), {
format: 'bgra',
})
}, [samples])
return <img ref={img} objectFit="fill" style={{ width: 800, height: 80 }} />
}
node-canvas 的 toBuffer('raw') 在小端机器上(所有 Apple Silicon、x86 与 ARM64 桌面机)是 BGRA,且无行内填充。它是预乘的,而 GPUIX 期望直通 alpha,两者只有在像素不透明时才一致,所以示例先填充背景。对于透明 canvas,请用 getImageData().data 并以默认的 'rgba' 格式。
波形示例 就是手写 BGRA、按 2x 上传的。
<svg>
<svg> 使用 GPUI 的单色图标渲染器。原始 source 在桌面端和浏览器里都有效。桌面应用也能用本地 src 路径。图标作为一个形状被绘制,并用 style.color 上色。
src 是文件系统路径或一个 data:image/svg+xml,… URL。Vitest 与部分 Bun 的 import … with { type: 'file' } 绑定会输出 data URL,GPUIX 两者都能解码。
Bun
用 Bun 的 text loader。该导入是一个包含完整 SVG 的字符串,bun build 会把它嵌入产物中。
import searchSvg from './assets/icons/search.svg' with { type: 'text' }
<svg
source={searchSvg}
style={{ width: 16, height: 16, color: '#b4b4b4' }}
/>
聊天示例就是这样用原始 SVG 源码构建出每个侧边栏与输入栏图标的。
Node.js
对于受支持的 Node.js 版本,相对模块读取一次图标即可。用 URL 可以跨操作系统保持路径正确,并避开 __dirname。
import { readFileSync } from 'node:fs'
const searchSvg = readFileSync(
new URL('./assets/icons/search.svg', import.meta.url),
'utf8',
)
<svg
source={searchSvg}
style={{ width: 16, height: 16, color: '#b4b4b4' }}
/>
Node.js 也有 text modules,但当前需要 --experimental-import-text。在 text import 不再需要运行时标志之前,优先用 readFileSync。