【发布时间】:2017-11-17 09:33:54
【问题描述】:
我正在尝试编写一些 JS,它使用 Canvas 来模拟 SVG 如何在图像上绘制形状,但我似乎无法让它工作。
我有一些 SVG 数据可以在脸上画出嘴巴。
<svg id="svgTag" style="background: url('https://image.ibb.co/h9CWiR/odd_size.png') no-repeat " xmlns="http://www.w3.org/2000/svg" version="1.0" width="379.000000" height="540.000000" viewBox="0 0 379.000000 540.000000" preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,540.0000) scale(1.000000,-1.000000)" fill="#000000" stroke="none">
<path d="M172 147
c-21 -1 -69 -8 -74 -10
l-2 -1 0 -32 0 -33 3 -4
c9 -14 31 -36 44 -45
12 -9 16 -11 27 -13
2 -1 8 -2 12 -2
9 -2 33 -3 34 -1
0 1 1 1 2 1
2 0 6 1 9 2
16 5 29 14 47 32
l9 8 0 44
c0 33 0 43 -1 44 -4 3 -31 8 -49 10 -14 1 -44 1 -61 0
z"/>
</g>
</svg>
我在这里有一个小提琴:https://jsfiddle.net/nhoughto5/5dhn1nm1/
我编写了一些 javascript,它从 SVG 元素中获取路径数据,对其进行解析,然后使用 Canvas 方法绘制同一张嘴。我已经实现了一个递归解决方案来逐步执行每个命令并以完全相同的方式绘制它。
const commandString = /[\-0-9,. ]+/ig;
const commandPattern = /[A-z]+/ig;
const stringSplit = /[, ]+/ig;
const shiftX = 0, shiftY = 0;
function cleanArray(array) {
const tmpArray = [];
for (let i = 0; i < array.length; i++) {
if (array[i].length > 0) {
tmpArray.push(parseInt(array[i]));
}
}
return tmpArray;
}
function move(array, context) {
let x = array[0];
let y = array[1];
context.moveTo(x + shiftX, y + shiftY);
//context.fill();
}
function quadraticTo(array, context) {
let x = array[0];
let y = array[1];
let x1 = array[2];
let y1 = array[3];
context.quadraticCurveTo(x + shiftX, y + shiftY, x1 + shiftX, y1 + shiftY);
context.fill();
if (array.length > 4) {
quadraticTo(array.splice(4, array.length), context);
}
}
function lineTo(array, context) {
let x = array[0];
let y = array[1];
context.lineTo(x + shiftX, y + shiftY);
context.fill();
if (array.length > 2) {
lineTo(array.splice(2, array.length), context);
}
}
function curveTo(array, context) {
let x = array[0];
let y = array[1];
let x1 = array[2];
let y1 = array[3];
let x2 = array[4];
let y2 = array[5];
context.bezierCurveTo(x + shiftX, y + shiftY, x1 + shiftX, y1 + shiftY, x2 + shiftX, y2 + shiftY);
context.fill();
if (array.length > 6) {
this._curveTo(array.splice(6, array.length), context);
}
}
function draw(path, context){
var commands = path.split(commandPattern);
var commandArray = path.split(commandString);
for (let i = 0, len = commandArray.length; i < len; i++) {
let action = commandArray[i].toUpperCase();
if (typeof commands[i] === 'undefined') break;
const array = cleanArray(commands[i].split(stringSplit));
switch (action) {
case "M":
move(array, context);
break;
case "C":
curveTo(array, context);
break;
case "Q":
quadraticTo(array, context);
break;
case "L":
lineTo(array, context);
break;
case "Z":
context.closePath();
break;
default:
}
}
}
function makeCanvas(canvas, ctx, img){
var svg = document.getElementById("svgTag");
var groups = svg.getElementsByTagName("g");
var path = groups[0].childNodes[1].getAttribute("d");
ctx.drawImage(img, 10, 10);
ctx.fillStyle = "black";
console.log(path);
draw(path, ctx);
}
function imageCanvas() {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.createElement("IMG");
img.addEventListener('load', function() {
makeCanvas(c, ctx, img);
}.bind(this), false);
img.setAttribute("src", "https://image.ibb.co/h9CWiR/odd_size.png");
}
window.onload = imageCanvas();
再次,我在这里创建了一个小提琴演示:https://jsfiddle.net/nhoughto5/whwxtxcp/1/
问题是它似乎完全错误。起初我认为这可能是 SVG 组中的 translate 值的问题。我在 JS 的顶部添加了一个常量,以便轻松翻译它。如果您将翻译常量修改为:const translateX = 100, translateY = 300; 它所绘制的内容将移至中间,您会发现它非常错误。
我已经逐步完成了我的解决方案,并检查了数据是否以正确的顺序拼接并绘制到画布上。画布是否与我不知道的 SVG 有什么不同?不要自吹自擂,但我相当有信心我的实现是正确的,但我缺少一些东西。
【问题讨论】:
-
乍一看,您似乎忽略了路径中绝对命令和相对命令之间的区别。另外我认为你在解析真实世界的 SVG 时会有很多惊喜,语法比看起来要糟糕得多。
-
@jcaron,我认为你是对的。反正有没有说明路径是如何定义的?我在 SVG 本身中看不到任何明显的东西。
-
@jcaron。 Nvm,回答了我自己的问题^。文档指出:“所有命令也有两种变体。大写字母指定页面上的绝对坐标,小写字母指定相对坐标(例如,从最后一点向上移动 10 像素,向左移动 7 像素)。”
标签: javascript canvas svg