临摹
目标:在drawCanvas函数中进行临摹,以正好压住背景的图像。
function drawCanvas() {
let centerX = canvas.clientWidth / 2;
ctx.lineWidth = 9;
let pathData = `
M ${centerX} 18 l -97 165 h 194 Z
M ${centerX} 82 l -36 68 h 72 Z
`;
let path = new Path2D(pathData);
ctx.strokeStyle = 'hsla(150, 50%, 50%, 0.7)';
ctx.stroke(path);
}
drawCanvas();
let bgCanvas;
let bgCtx;
function initBG() {
bgCanvas = document.querySelector('#bg-canvas');
bgCanvas.style.width = bgCanvas.clientWidth + 'px';
bgCanvas.style.height = bgCanvas.clientHeight + 'px';
bgCanvas.width = bgCanvas.clientWidth * devicePixelRatio;
bgCanvas.height = bgCanvas.clientHeight * devicePixelRatio;
bgCtx = bgCanvas.getContext('2d');
bgCtx.scale(devicePixelRatio, devicePixelRatio);
bgCtx.clearRect(0, 0, bgCanvas.clientWidth, bgCanvas.clientHeight);
}
function drawBG(char, fontSize, color) {
bgCtx.font = `${fontSize}px Helvetica`;
bgCtx.textAlign = 'center';
bgCtx.textBaseline = 'middle';
bgCtx.fillStyle = color;
bgCtx.fillText(char, bgCanvas.clientWidth / 2, bgCanvas.clientHeight / 2);
}
initBG();
drawBG('\u{27C1}', 250, '#888');
body {
display: grid;
}
#canvas, #bg-canvas {
grid-row: 1;
grid-column: 1;
}
#canvas {
z-index: 10;
}
将drawCanvas函数调用屏蔽掉,则可看出使用灰色来绘制的背景图像。恢复drawCanvas函数调用,可以看出,上下两个图层完全匹配。
我们通过编写代码的方式,将任意的一个背景图像进行了精准的临摹,临摹的结果,是我们拥有了一套能生成特定图像的Canvas 2D的代码。
有了这段代码,我们一是可以在其基础上进行再创作;二是可以精准居中;三是可生成data:URL;四是应用滤镜,改变图像的效果;五是将其转化为其他格式的图像。
下节,我们将其在线转换为SVG图像格式并提供下载链接。
Canvas转换为SVG格式
Canvas 2D是基于BMP(位图)的绘图方式,SVG是一种矢量的绘图方式。将位图转换为矢量图的过程称为矢量化(vectorization)过程,即将像素图像转换为由路径、形状和颜色组成的矢量图形。
矢量化过程是一个较为复杂的过程,目前尚没有直接将位图向SVG矢量化的规范。我们可以借助于诸如 Adobe Illustrator, Vector Magic, Vectormator 等相关的应用软件,或者通过诸如Potrace.js, ImageTracer.js, Sharp.js, Canva2SVG, OpenCV.js等第三方的JavaScript类库来实现目标,但总体说来,要么效果不大理想,要么学习曲线陡峭,要么需付费使用。简而言之,总有不尽人意的地方。
矢量化的本质是将像素转换为数学路径。好消息是,Canvas 2D有Path2D,而SVG有path标签,这为矢量化过程搭建了一个很好的桥梁。
以此为抓手,我们可以不使用任何第三方软件或类库,就可将Canvas 2D所绘制的图像,直接转换为SVG图像。
const CENTER_X = canvas.clientWidth / 2;
const pathWrapper = {
lineWidth: 9,
data: `
M ${CENTER_X} 18 l -97 165 h 194 Z
M ${CENTER_X} 82 l -36 68 h 72 Z
`,
strokeColor: 'hsla(150, 50%, 50%, 1)',
fillColor: 'transparent'
};
let svgContentStr;
function drawCanvas() {
ctx.lineWidth = pathWrapper.lineWidth;
let path = new Path2D(pathWrapper.data);
ctx.strokeStyle = pathWrapper.strokeColor;
ctx.stroke(path);
ctx.fillStyle = pathWrapper.fillColor;
ctx.fill(path);
}
function createSVG() {
const SVG_NAME_SPACE = 'http://www.w3.org/2000/svg';
let svg = document.createElementNS(SVG_NAME_SPACE, 'svg');
svg.setAttribute('xmlns', SVG_NAME_SPACE);
svg.setAttribute('version', '1.1');
svg.setAttribute('width', '150');
svg.setAttribute('height', '150');
svg.setAttribute('viewBox', `0 0 ${canvas.clientWidth} ${canvas.clientHeight}`);
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
let path = document.createElementNS(svg.namespaceURI, 'path');
path.setAttribute('stroke-width', pathWrapper.lineWidth);
path.setAttribute('d', pathWrapper.data);
path.setAttribute('stroke', pathWrapper.strokeColor);
path.setAttribute('fill', pathWrapper.fillColor);
svg.appendChild(path);
document.querySelector('#output').appendChild(svg);
svgContentStr = new XMLSerializer().serializeToString(svg);
console.log(svgContentStr);
}
function createDownloadLink() {
let file = new File([svgContentStr], "two-triangles.svg", {type: "image/svg"});
let url = URL.createObjectURL(file);
const link = document.createElement('a');
link.href = url;
link.download = file.name;
link.textContent = `Click to download`;
document.querySelector('#output').appendChild(link);
}
drawCanvas();
createSVG();
createDownloadLink();
body {
display: grid;
grid-template-columns: auto auto;
gap: 1em;
}
canvas {
width: 250px;
height: 200px;
}
#output {
display: flex;
flex-direction: column;
gap: 1em;
padding: 0.5em;
border: 0.1px solid gray;
width: fit-content;
& svg {
border: 0.11px solid gray;
margin: 0 auto;
}
& a {
color: #50B7E0;
text-decoration: none;
text-align: center;
&:hover {
color: #FCEE59;
}
}
}
我们将Canvas 2D及SVG均需要使用的路径数据,抽象为一个名为pathWrapper的对象,同时供给drawCanvas及createSVG函数使用。由于数据源相同,则创建出来的两种格式的图像内容完全相同。而若需要创建更复杂的图形,可通过构建多个子路径的方式来完成。
SVG的viewBox是一个可自动转换坐标系及可实现自动居中的属性,利用此特性,我们将canvas的宽高值用以设置该属性值,这样,无论源图像的尺寸是多少,我们可以随意定制所生成的SVG的尺寸,最终都会按比例进行缩放并在水平、垂直方向上同时予以居中。具体细节,参见SVG坐标系。
创建好svg后,我们通过XMLSerializer的serializeToString方法,得到了整个SVG内容的字符串,打开console
面板即可看到它。利用该字符串,我们既可生成一个独立的SVG文件,也可将其作为data:URL的数据源来直接使用。而在例子中,通过调用URL的createObjectURL方法,传入一个指定了文件名及MIME Type的File对象,创建了一个URL,最终创建一个HTMLAnchorElement实例,生成了一个可点击下载的链接。