| 功能类别 | 已实现 | 说明 |
|---|---|---|
| 基础绘图 | ✅ | 矩形、路径、圆弧、贝塞尔曲线 |
| 样式设置 | ✅ | 填充色、描边色、线宽、透明度 |
| 变换操作 | ✅ | 平移、旋转、缩放、变换矩阵 |
| 状态管理 | ✅ | save/restore |
| 图像操作 | ✅ | 加载、绘制图像 |
| 虚线样式 | ✅ | setLineDash, lineDashOffset |
| 裁剪 | ✅ | clip() |
| 像素操作 | ✅ | getImageData, putImageData, createImageData |
| 多格式支持 | ✅ | PNG, JPEG, WebP (导入/导出) |
| 文字渲染 | ❌ | fillText, strokeText, measureText |
| 渐变 | ❌ | createLinearGradient, createRadialGradient |
| 图案填充 | ❌ | createPattern |
| 阴影效果 | ❌ | shadowBlur, shadowColor, shadowOffset |
pip install never-jscorefrom never_jscore import Context
# 创建 JavaScript 上下文(启用 Canvas 扩展)
ctx = Context(enable_extensions=True)
# 创建 Canvas
code = """
const canvas = createCanvas(800, 600);
const ctx = canvas.getContext('2d');
// 绘制红色矩形
ctx.fillStyle = '#FF0000';
ctx.fillRect(50, 50, 200, 100);
// 导出为 PNG
const buffer = canvas.toBuffer('image/png');
buffer;
"""
result = ctx.eval(code)
# 保存图像
with open('output.png', 'wb') as f:
f.write(bytes(result))from never_jscore import Context
ctx = Context(enable_extensions=True)
code = """
// 创建 800x600 画布
const canvas = createCanvas(800, 600);
const ctx = canvas.getContext('2d');
// 1. 背景
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, 800, 600);
// 2. 绘制渐变色矩形(使用纯色模拟)
ctx.fillStyle = '#4CAF50';
ctx.fillRect(50, 50, 200, 150);
// 3. 绘制描边圆角矩形
ctx.strokeStyle = '#2196F3';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(300, 50);
ctx.lineTo(480, 50);
ctx.arcTo(500, 50, 500, 70, 20);
ctx.lineTo(500, 180);
ctx.arcTo(500, 200, 480, 200, 20);
ctx.lineTo(300, 200);
ctx.arcTo(280, 200, 280, 180, 20);
ctx.lineTo(280, 70);
ctx.arcTo(280, 50, 300, 50, 20);
ctx.closePath();
ctx.stroke();
// 4. 绘制圆形路径
ctx.fillStyle = '#FF5722';
ctx.beginPath();
ctx.arc(400, 350, 80, 0, Math.PI * 2);
ctx.fill();
// 5. 绘制虚线
ctx.setLineDash([10, 5]);
ctx.strokeStyle = '#9C27B0';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(50, 450);
ctx.lineTo(750, 450);
ctx.stroke();
ctx.setLineDash([]); // 重置虚线
// 6. 使用变换绘制旋转矩形
ctx.save();
ctx.translate(650, 100);
ctx.rotate(Math.PI / 4);
ctx.fillStyle = '#FFC107';
ctx.fillRect(-50, -50, 100, 100);
ctx.restore();
// 7. 裁剪区域绘制
ctx.save();
ctx.beginPath();
ctx.arc(150, 350, 60, 0, Math.PI * 2);
ctx.clip();
ctx.fillStyle = '#E91E63';
ctx.fillRect(90, 290, 120, 120);
ctx.restore();
// 8. 设置透明度
ctx.globalAlpha = 0.5;
ctx.fillStyle = '#00BCD4';
ctx.fillRect(550, 400, 150, 100);
ctx.globalAlpha = 1.0;
// 导出为 PNG
canvas.toBuffer('image/png');
"""
result = ctx.eval(code)
with open('comprehensive_example.png', 'wb') as f:
f.write(bytes(result))
print("✅ 综合绘图示例已保存到 comprehensive_example.png")from never_jscore import Context
ctx = Context(enable_extensions=True)
code = """
// 加载图像
const img = new Image();
img.src = 'input.png';
// 创建画布
const canvas = createCanvas(800, 600);
const ctx = canvas.getContext('2d');
// 绘制背景
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, 800, 600);
// 绘制原始图像
ctx.drawImage(img, 50, 50);
// 绘制缩放图像
ctx.drawImage(img, 300, 50, 200, 150);
// 绘制裁剪图像(从源图像截取部分)
ctx.drawImage(img, 0, 0, 100, 100, 550, 50, 150, 150);
// 像素操作:反色滤镜
const imageData = ctx.getImageData(50, 250, 200, 150);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // R
data[i + 1] = 255 - data[i + 1]; // G
data[i + 2] = 255 - data[i + 2]; // B
// data[i + 3] 保持不变 (Alpha)
}
ctx.putImageData(imageData, 50, 250);
// 导出为 JPEG(质量 90)
canvas.toBuffer('image/jpeg');
"""
result = ctx.eval(code)
with open('image_processing.jpg', 'wb') as f:
f.write(bytes(result))
print("✅ 图像处理示例已保存到 image_processing.jpg")from never_jscore import Context
import os
ctx = Context(enable_extensions=True)
# 生成 60 帧动画
for frame in range(60):
code = f"""
const canvas = createCanvas(400, 400);
const ctx = canvas.getContext('2d');
// 背景
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, 0, 400, 400);
// 旋转的正方形
const angle = ({frame} / 60) * Math.PI * 2;
ctx.save();
ctx.translate(200, 200);
ctx.rotate(angle);
ctx.fillStyle = '#00ff00';
ctx.fillRect(-50, -50, 100, 100);
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 3;
ctx.strokeRect(-50, -50, 100, 100);
ctx.restore();
// 帧数显示
ctx.fillStyle = '#ffffff';
// ctx.font = '20px Arial'; // 文字渲染未实现
// ctx.fillText('Frame: {frame}', 10, 30);
canvas.toBuffer('image/png');
"""
result = ctx.eval(code)
with open(f'frames/frame_{frame:03d}.png', 'wb') as f:
f.write(bytes(result))
print("✅ 60 帧动画已生成到 frames/ 目录")from never_jscore import Context
ctx = Context(enable_extensions=True)
code = """
const canvas = createCanvas(800, 600);
const ctx = canvas.getContext('2d');
// 背景
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, 800, 600);
// 数据
const data = [65, 59, 80, 81, 56, 55, 40];
const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
// 参数
const chartX = 100;
const chartY = 100;
const chartWidth = 600;
const chartHeight = 400;
const barWidth = chartWidth / data.length;
const maxValue = Math.max(...data);
// 绘制坐标轴
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(chartX, chartY);
ctx.lineTo(chartX, chartY + chartHeight);
ctx.lineTo(chartX + chartWidth, chartY + chartHeight);
ctx.stroke();
// 绘制柱状图
data.forEach((value, index) => {
const barHeight = (value / maxValue) * chartHeight;
const x = chartX + index * barWidth + 10;
const y = chartY + chartHeight - barHeight;
// 柱子
ctx.fillStyle = '#4CAF50';
ctx.fillRect(x, y, barWidth - 20, barHeight);
// 边框
ctx.strokeStyle = '#2E7D32';
ctx.lineWidth = 2;
ctx.strokeRect(x, y, barWidth - 20, barHeight);
});
// 标题(文字渲染未实现,这里用矩形模拟)
ctx.fillStyle = '#333';
ctx.fillRect(300, 30, 200, 40);
canvas.toBuffer('image/png');
"""
result = ctx.eval(code)
with open('chart_example.png', 'wb') as f:
f.write(bytes(result))
print("✅ 图表示例已保存到 chart_example.png")const canvas = new Canvas(width, height);
// 或使用工厂函数
const canvas = createCanvas(width, height);参数:
width(number): 画布宽度(像素),默认 300height(number): 画布高度(像素),默认 150
示例:
const canvas = createCanvas(800, 600);画布宽度(可读写)
canvas.width = 1024;
console.log(canvas.width); // 1024画布高度(可读写)
canvas.height = 768;
console.log(canvas.height); // 768获取绘图上下文
参数:
contextType(string): 上下文类型,目前仅支持"2d"
返回值: CanvasRenderingContext2D | null
const ctx = canvas.getContext('2d');导出为图像缓冲区(Uint8Array)
参数:
mimeType(string): MIME 类型"image/png"- PNG 格式(默认)"image/jpeg"- JPEG 格式"image/webp"- WebP 格式
返回值: Array<number> (可转换为 Uint8Array)
// PNG
const pngBuffer = canvas.toBuffer('image/png');
// JPEG (质量 90)
const jpegBuffer = canvas.toBuffer('image/jpeg');
// WebP (无损)
const webpBuffer = canvas.toBuffer('image/webp');导出为 Base64 Data URL
参数:
type(string): MIME 类型,默认"image/png"quality(number): 图像质量 (0-1),仅 JPEG 有效
返回值: string
const dataUrl = canvas.toDataURL('image/png');
// data:image/png;base64,iVBORw0KG...
const jpegDataUrl = canvas.toDataURL('image/jpeg', 0.9);导出为 Blob 对象(异步)
参数:
callback(function): 回调函数(blob) => {}type(string): MIME 类型quality(number): 图像质量
canvas.toBlob((blob) => {
console.log(blob.size);
}, 'image/png');ctx.fillStyle = '#FF0000'; // CSS 颜色
ctx.fillStyle = 'rgb(255, 0, 0)';
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
ctx.strokeStyle = '#0000FF';ctx.lineWidth = 5; // 线宽
ctx.lineCap = 'butt'; // 线帽: butt | round | square
ctx.lineJoin = 'miter'; // 线连接: miter | round | bevel
ctx.miterLimit = 10; // 斜接限制ctx.setLineDash([10, 5]); // 虚线模式 [实线长度, 间隙长度, ...]
ctx.lineDashOffset = 0; // 虚线偏移
const dash = ctx.getLineDash(); // 获取虚线模式ctx.globalAlpha = 0.5; // 全局透明度 (0-1)ctx.font = '20px Arial'; // ❌ 未实现
ctx.textAlign = 'left'; // ❌ 未实现
ctx.textBaseline = 'alphabetic'; // ❌ 未实现绘制填充矩形
ctx.fillStyle = '#FF0000';
ctx.fillRect(50, 50, 200, 100);绘制描边矩形
ctx.strokeStyle = '#0000FF';
ctx.lineWidth = 3;
ctx.strokeRect(50, 50, 200, 100);清除矩形区域(变为透明)
ctx.clearRect(50, 50, 200, 100);开始新路径
ctx.beginPath();闭合当前路径
ctx.closePath();移动到指定点(不绘制)
ctx.moveTo(100, 100);绘制直线到指定点
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 200);
ctx.stroke();绘制圆弧
参数:
x,y: 圆心坐标radius: 半径startAngle: 起始角度(弧度)endAngle: 结束角度(弧度)anticlockwise: 是否逆时针,默认false
// 绘制完整圆
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.fill();
// 绘制半圆
ctx.beginPath();
ctx.arc(300, 200, 50, 0, Math.PI);
ctx.stroke();绘制圆角(未实现)
// ❌ 未实现
ctx.arcTo(100, 100, 200, 100, 20);绘制二次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.quadraticCurveTo(150, 50, 250, 100);
ctx.stroke();绘制三次贝塞尔曲线
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.bezierCurveTo(100, 50, 200, 150, 250, 100);
ctx.stroke();添加矩形路径
ctx.beginPath();
ctx.rect(50, 50, 100, 100);
ctx.stroke();填充当前路径
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.fillStyle = '#FF0000';
ctx.fill();描边当前路径
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.strokeStyle = '#0000FF';
ctx.lineWidth = 3;
ctx.stroke();裁剪到当前路径
ctx.beginPath();
ctx.arc(200, 200, 100, 0, Math.PI * 2);
ctx.clip();
// 后续绘制只在圆形区域内可见
ctx.fillStyle = '#FF0000';
ctx.fillRect(0, 0, 400, 400);保存当前绘图状态
ctx.save();
ctx.fillStyle = '#FF0000';
ctx.restore(); // 恢复之前的状态恢复之前保存的状态
ctx.save();
ctx.fillStyle = '#FF0000';
ctx.restore();
console.log(ctx.fillStyle); // 恢复到保存前的值平移坐标系
ctx.translate(100, 100);
ctx.fillRect(0, 0, 50, 50); // 实际绘制在 (100, 100)旋转坐标系
参数:
angle: 旋转角度(弧度)
ctx.translate(200, 200);
ctx.rotate(Math.PI / 4); // 旋转 45 度
ctx.fillRect(-25, -25, 50, 50);缩放坐标系
ctx.scale(2, 2); // 放大 2 倍
ctx.fillRect(0, 0, 50, 50); // 实际绘制 100x100设置变换矩阵
ctx.setTransform(1, 0, 0, 1, 100, 100); // 平移到 (100, 100)重置变换矩阵为单位矩阵
ctx.resetTransform();绘制图像
三种调用形式:
- 基本形式:
drawImage(image, dx, dy)
ctx.drawImage(img, 50, 50);- 缩放形式:
drawImage(image, dx, dy, dWidth, dHeight)
ctx.drawImage(img, 50, 50, 200, 150);- 裁剪形式:
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
// 从源图像 (10, 10) 处裁剪 100x100,绘制到 (50, 50),缩放为 200x150
ctx.drawImage(img, 10, 10, 100, 100, 50, 50, 200, 150);创建空白 ImageData 对象
const imageData = ctx.createImageData(100, 100);
// 所有像素初始化为透明黑色 (0, 0, 0, 0)获取像素数据
返回值: ImageData 对象
const imageData = ctx.getImageData(50, 50, 100, 100);
console.log(imageData.width); // 100
console.log(imageData.height); // 100
console.log(imageData.data); // Uint8ClampedArray [R, G, B, A, ...]放置像素数据
const imageData = ctx.getImageData(0, 0, 100, 100);
// 反色滤镜
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i] = 255 - imageData.data[i]; // R
imageData.data[i + 1] = 255 - imageData.data[i + 1]; // G
imageData.data[i + 2] = 255 - imageData.data[i + 2]; // B
}
ctx.putImageData(imageData, 0, 0);ctx.fillText('Hello', 50, 50); // ❌ 未实现
ctx.strokeText('World', 50, 100); // ❌ 未实现
const metrics = ctx.measureText('Test'); // ❌ 未实现const img = new Image();图像源(文件路径或 Data URL)
img.src = 'path/to/image.png';
img.src = 'data:image/png;base64,...';图像宽度
console.log(img.width);图像高度
console.log(img.height);图像是否加载完成
if (img.complete) {
ctx.drawImage(img, 0, 0);
}图像加载完成回调
img.onload = function() {
console.log('Image loaded:', img.width, img.height);
};
img.src = 'image.png';图像加载失败回调
img.onerror = function(error) {
console.error('Failed to load image:', error);
};释放图像内存
img.dispose();加载图像(返回 Promise)
参数:
source(string | Uint8Array): 文件路径、Data URL 或图像缓冲区
返回值: Promise<Image>
// 从文件加载
const img = await loadImage('image.png');
// 从 Buffer 加载
const buffer = new Uint8Array([...]);
const img = await loadImage(buffer);
// 从 Data URL 加载
const img = await loadImage('data:image/png;base64,...');const imageData = new ImageData(data, width, height);参数:
data(Uint8ClampedArray): 像素数据 (RGBA 格式)width(number): 宽度height(number): 高度
像素数据数组(Uint8ClampedArray)
每个像素 4 个字节:[R, G, B, A, R, G, B, A, ...]
const imageData = ctx.getImageData(0, 0, 100, 100);
const pixels = imageData.data;
// 访问第一个像素
const r = pixels[0];
const g = pixels[1];
const b = pixels[2];
const a = pixels[3];
// 设置为红色
pixels[0] = 255; // R
pixels[1] = 0; // G
pixels[2] = 0; // B
pixels[3] = 255; // A宽度
console.log(imageData.width);高度
console.log(imageData.height);| 功能 | Web API | never-jscore | 兼容性 |
|---|---|---|---|
| 基础 | |||
| Canvas 创建 | <canvas> 或 new OffscreenCanvas() |
createCanvas(w, h) |
|
| 获取上下文 | canvas.getContext('2d') |
canvas.getContext('2d') |
✅ 100% |
| 导出 PNG | canvas.toDataURL() |
canvas.toDataURL() |
✅ 100% |
| 导出 JPEG | canvas.toDataURL('image/jpeg') |
canvas.toBuffer('image/jpeg') |
✅ 支持 |
| 导出 WebP | canvas.toDataURL('image/webp') |
canvas.toBuffer('image/webp') |
✅ 支持 |
| 矩形 | |||
| fillRect | ✅ | ✅ | ✅ 100% |
| strokeRect | ✅ | ✅ | ✅ 100% |
| clearRect | ✅ | ✅ | ✅ 100% |
| 路径 | |||
| beginPath | ✅ | ✅ | ✅ 100% |
| closePath | ✅ | ✅ | ✅ 100% |
| moveTo | ✅ | ✅ | ✅ 100% |
| lineTo | ✅ | ✅ | ✅ 100% |
| arc | ✅ | ✅ | ✅ 100% |
| arcTo | ✅ | ❌ | ❌ 未实现 |
| quadraticCurveTo | ✅ | ✅ | ✅ 100% |
| bezierCurveTo | ✅ | ✅ | ✅ 100% |
| rect | ✅ | ✅ | ✅ 100% |
| fill | ✅ | ✅ | ✅ 100% |
| stroke | ✅ | ✅ | ✅ 100% |
| clip | ✅ | ✅ | ✅ 100% |
| 样式 | |||
| fillStyle | ✅ | ✅ | ✅ 100% |
| strokeStyle | ✅ | ✅ | ✅ 100% |
| lineWidth | ✅ | ✅ | ✅ 100% |
| lineCap | ✅ | ||
| lineJoin | ✅ | ||
| globalAlpha | ✅ | ✅ | ✅ 100% |
| setLineDash | ✅ | ✅ | ✅ 100% |
| getLineDash | ✅ | ✅ | ✅ 100% |
| lineDashOffset | ✅ | ✅ | ✅ 100% |
| 变换 | |||
| save | ✅ | ✅ | ✅ 100% |
| restore | ✅ | ✅ | ✅ 100% |
| translate | ✅ | ✅ | ✅ 100% |
| rotate | ✅ | ✅ | ✅ 100% |
| scale | ✅ | ✅ | ✅ 100% |
| transform | ✅ | ❌ | ❌ 未实现 |
| setTransform | ✅ | ✅ | ✅ 100% |
| resetTransform | ✅ | ✅ | ✅ 100% |
| 图像 | |||
| drawImage | ✅ | ✅ | ✅ 100% |
| createImageData | ✅ | ✅ | ✅ 100% |
| getImageData | ✅ | ✅ | ✅ 100% |
| putImageData | ✅ | ✅ | ✅ 100% |
| 文字 | |||
| fillText | ✅ | ❌ | ❌ 未实现 |
| strokeText | ✅ | ❌ | ❌ 未实现 |
| measureText | ✅ | ❌ | ❌ 未实现 |
| 渐变 | |||
| createLinearGradient | ✅ | ❌ | ❌ 未实现 |
| createRadialGradient | ✅ | ❌ | ❌ 未实现 |
| createConicGradient | ✅ | ❌ | ❌ 未实现 |
| 图案 | |||
| createPattern | ✅ | ❌ | ❌ 未实现 |
| 阴影 | |||
| shadowBlur | ✅ | ❌ | ❌ 未实现 |
| shadowColor | ✅ | ❌ | ❌ 未实现 |
| shadowOffsetX | ✅ | ❌ | ❌ 未实现 |
| shadowOffsetY | ✅ | ❌ | ❌ 未实现 |
| 功能 | node-canvas | never-jscore | 兼容性 |
|---|---|---|---|
| 创建 Canvas | createCanvas(w, h) |
createCanvas(w, h) |
✅ 100% |
| 图像加载 | loadImage(src) |
loadImage(src) |
✅ 100% |
| PNG 导出 | canvas.toBuffer('image/png') |
canvas.toBuffer('image/png') |
✅ 100% |
| JPEG 导出 | canvas.toBuffer('image/jpeg') |
canvas.toBuffer('image/jpeg') |
✅ 100% |
| PDF 导出 | ✅ | ❌ | ❌ 不支持 |
| SVG 后端 | ✅ | ❌ | ❌ 仅支持光栅化 |
| 文字渲染 | ✅ | ❌ | ❌ 未实现 |
| 系统字体 | ✅ | ❌ | ❌ 未实现 |
❌ 低效:
for (let i = 0; i < 1000; i++) {
ctx.fillRect(i, i, 10, 10);
}✅ 高效:
ctx.beginPath();
for (let i = 0; i < 1000; i++) {
ctx.rect(i, i, 10, 10);
}
ctx.fill();❌ 低效:
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 10, 10);
ctx.fillStyle = 'blue';
ctx.fillRect(10, 10, 10, 10);
ctx.fillStyle = 'red';
ctx.fillRect(20, 20, 10, 10);✅ 高效:
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 10, 10);
ctx.fillRect(20, 20, 10, 10);
ctx.fillStyle = 'blue';
ctx.fillRect(10, 10, 10, 10);像素操作非常耗时,尽量一次性处理大块区域:
// 一次性获取整个区域
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 批量处理
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
}
// 一次性写回
ctx.putImageData(imageData, 0, 0);- PNG: 无损,支持透明,文件较大
- JPEG: 有损,不支持透明,文件小,适合照片
- WebP: 现代格式,压缩率高
// 照片类图像使用 JPEG
const jpegBuffer = canvas.toBuffer('image/jpeg');
// 图标、UI 使用 PNG
const pngBuffer = canvas.toBuffer('image/png');
// 平衡质量和大小使用 WebP
const webpBuffer = canvas.toBuffer('image/webp');暂时无法使用 fillText(), strokeText(), measureText()
替代方案: 使用图像或预渲染文字
暂时无法使用 createLinearGradient(), createRadialGradient()
替代方案: 使用纯色或自己绘制渐变效果
暂时无法使用 shadowBlur, shadowColor 等
替代方案: 手动绘制阴影层
- 圆弧使用线段近似(质量略低于原生实现)
- 某些高级混合模式可能不支持
新增功能:
- ✅ 虚线样式 (setLineDash, getLineDash, lineDashOffset)
- ✅ 裁剪功能 (clip)
- ✅ 像素操作 (getImageData, putImageData, createImageData)
- ✅ JPEG 格式支持(导入/导出)
- ✅ WebP 格式支持(导入/导出)
改进:
- 优化图像加载性能
- 修复 premultiplied alpha 转换问题
A: 先下载到本地,然后加载:
import requests
from never_jscore import Context
# 下载图片
response = requests.get('https://example.com/image.png')
with open('temp.png', 'wb') as f:
f.write(response.content)
# 在 JS 中加载
ctx = Context(enable_extensions=True)
code = """
const img = new Image();
img.src = 'temp.png';
// 使用 img...
"""A: 文字渲染功能尚未实现。建议使用图像或预渲染文字。
A: 渐变功能尚未实现。可以手动绘制渐变效果:
// 手动绘制线性渐变
for (let i = 0; i < 100; i++) {
const ratio = i / 100;
const r = Math.floor(255 * ratio);
const g = Math.floor(128 * (1 - ratio));
ctx.fillStyle = `rgb(${r}, ${g}, 0)`;
ctx.fillRect(i, 0, 1, 100);
}欢迎贡献代码!尚未实现的功能:
- 文字渲染 - 需要集成字体引擎
- 渐变 - tiny-skia 支持,需要封装
- 图案填充 - tiny-skia 支持,需要封装
- 阴影效果 - 需要实现高斯模糊
MIT License