CV工程师
2022-11-02 11:31:33 阅读:884
前一段时间看到群友再说想要将png图片转换成svg图,感觉挺好玩的,于是就想要做这样一个工具出来。
发布npm依赖:https://www.npmjs.com/package/png-to-svg-wasm
源代码:https://github.com/xuxinyuancode/png-to-svg-wasm
目前工具已经做好了:在线Png转SVG,然而其中却踩坑无数,且听我娓娓道来。
如何能够将Png转换成SVG呢?我们都知道前者是位图,而后者是矢量图,如何能够将位图转换成矢量图呢?我不禁陷入了沉思,但想来想去也没有一个好的办法,于是只能去看看有没有前辈开发出这样的工具出来,百度搜索一番后发现,果然有,经过分析,目前市面上有的PNG转SVG图工具一共有两种:一种是先将png图转成base64,然后svg内直接使用这个base64数据,这和直接使用png没有区别;第二种是遍历png的每一个像素,转换成svg的点,最后想办法压缩一下。
实际上我发现这两种方式无论哪一种本质上还是位图,但是第二种方式在压缩时可以在有些地方做到矢量,在没有其他办法的情况下只能这样做了。
确定好技术路线以后我还是一贯的打开 https://www.npmjs.com/ 以及 GitHub搜索png to svg
,然而npmjs中根本没有对应的依赖,GitHub中仅发现了python版本的,看来没有轮子能用了,只能自己造了。
想到这,我又难受了,如果用js遍历像素,效率会不会特别低下呢?毕竟我知道了前端还有个叫做wasm
的玩意,经过一番研究,于是决定自己造一个wasm
轮子,技术选型有:1. go, 2. rust。这俩是我从来没有接触过的语言,只能看谁更容易搭建框架了,最终决定使用rust。
我本着站在巨人的肩膀上的想法百度了一番rust开发wasm,找到一堆博文,最后发现各种问题。于是决定还是看文档吧,果然还是官方文档效率最高。
我的开发环境时Centos
安装rust本身,在Linux下安装rust非常简单,仅需要一条命令(下载安装脚本):
curl https://sh.rustup.rs -sSf | sh
安装wasm-pack,这是一个用于构建wasm应用程序的工具,安装同样仅需要一条简单的命令:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
到这里,大的工具就安装完了,这里简单说一下以上两步我们都安装了哪些东西: rustc:这是一个编译器,可以简单理解为node rustup:管理编译器的工具,简单理解为nvm cargo:包管理工具,类似npm或者yarn
看到这些我想根据流程已经不难猜出下一步就是要安装包了,没错,我们来安装第一个包:cargo-generate:
cargo install cargo-generate
它类似于cli,通过利用预先存在的 git 仓库作为模板,帮助您快速启动和运行新的 Rust 项目。
还需要安装一下npm,这部分直接跳过。
之后我们来拉一个模板出来,之后的开发就在模板的基础上进行:
cargo generate --git https://github.com/rustwasm/wasm-pack-template
上面我们已经做好了开发前的准备,具体内容可以亲自尝试。下面我就要想办法使用rust开发出一个png转svg的工具出来,由于我是一个从未接触rust的人,所以
漫长的了解过后...
我做了第一步:
cargo install image --bins
给rust安装了一个叫做image的依赖,后面带有一个bins的参数,如果有用到的朋友一定要记得加(说多了都是泪...)
这个image可以帮助我们获取到图片的像素点,然后就很简单了,拿到图片的宽高,一个嵌套循环遍历所有的像素点,再把像素点的rgba拿出来,转换成一个个的svg点,拼接一下就可以了。是不是很简单?是的没错...这话我说的都不信,其实主要还是语法问题,rust给我的感觉有点像c语言了,还有指针,str和string是不一样的。。。str可以拼接,string不行!!!不懂。。。不影响开发就行,代码如下:
mod utils;
use image::{
codecs::jpeg::JpegEncoder, imageops::FilterType, load_from_memory, ColorType, GenericImageView,
};
use std::str;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
pub fn get_svg(raw: Vec<u8>) -> String {
let img = load_from_memory(&raw).unwrap();
let mut result = svg_header(img.width(), img.height());
let mut temX: u32 = 0;
let mut temY: u32 = 0;
let mut temW: u32 = 0;
let mut temH: u32 = 0;
let mut temR: f32 = 0.0;
let mut temG: f32 = 0.0;
let mut temB: f32 = 0.0;
let mut temA: f32 = 0.0;
for x in 1..img.width() {
for y in 1..img.height() {
let rgba = img.get_pixel(x, y);
let _r = rgba.0[0] as f32;
let _g = rgba.0[1] as f32;
let _b = rgba.0[2] as f32;
let _a = rgba.0[3] as f32;
temX = x;
temY = y;
temW = 1;
temH = 1;
temR = _r;
temG = _g;
temB = _b;
temA = _a;
result += &getRect(temX, temY, temW, temH, temR, temG, temB, temA);
}
}
result += "</svg>";
result
}
pub fn svg_header(width: u32, height: u32) -> String {
let mut result = "".to_string();
result += &"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"
\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">
<svg width=\"";
result += (format!("{}", width)).as_str();
result += "\" height=\"";
result += (format!("{}", height)).as_str();
result += "\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">";
return result;
}
pub fn getRect(x: u32, y: u32, w: u32, h: u32, _r: f32, _g: f32, _b: f32, _a: f32) -> String {
let result = format!("<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"1\" style=\"fill:rgb({}, {}, {}); fill-opacity:{};\" />",x,y,w,_r,_g,_b,_a);
return result.to_string();
}
开发完成!
wasm-pack publish
我发布到了npmjs上: https://www.npmjs.com/package/png-to-svg-wasm
注意:如果发布后删除,请再24小时后重新发布,直接重新发布会报错: [...] npm ERR! code E404 npm ERR! 404 Not Found - PUT https://registry.npmjs.org/g1link - Not found npm ERR! 404 npm ERR! 404 'png-to-svg-wasm' is not in the npm registry. npm ERR! 404 You should bug the author to publish it (or use the name yourself!) npm ERR! 404 npm ERR! 404 Note that you can also install from a npm ERR! 404 tarball, folder, http url, or git url. [...] 千万要记住了,可能是删除包后又重新发布造成的,过几个小时后才会提醒你要24小时后发布,不要再找其他原因了,比如什么重新登录啊,更换token什么的了,前提是你真的删除项目了。
打包比较简单:wasm-pack build
,打包完成以后我创建了一个node项目,用于测试:
https://github.com/rustwasm/create-wasm-app
直接拉这个项目,然后稍加修改,就ok了,测试没有问题。
复制生成的pkg中的文件到nuxt3中以后发现用不了。。。报错我忘记了,经过一番苦苦的查找,原来是因为在build的时候没有生成浏览器所使用的包,怪我,不看文档。原文:https://rustwasm.github.io/wasm-pack/book/commands/build.html 关键信息: 该build命令接受一个--target参数。这将自定义发出的 JS 以及如何实例化和加载 WebAssembly 文件。有关此处各种策略的更多文档,请参阅有关使用编译输出的文档。
wasm-pack build --target nodejs
选项 | 用法 | 描述 |
---|---|---|
未指定或bundler | 捆绑器 | 输出适合与 Webpack 等 Bundler 互操作的 JS。您将importJS 和module密钥在package.json. sideEffects: false默认情况下。 |
nodejs | node.js | 输出使用 CommonJS 模块的 JS,用于require语句。main键入package.json. |
web | 原生浏览器 | 输出可以在浏览器中作为 ES 模块本地导入的 JS,但 WebAssembly 必须手动实例化和加载。 |
no-modules | 浏览器原生 | 与 相同web,只是 JS 包含在页面中并修改全局状态,并且不支持wasm-bindgen与web |
知道问题以后我们使用命令重新打包:
wasm-pack build --target web
ok,没有问题。
插播一个意外,现在无法重现了,就是需要安装两个插件,在nuxt.config.ts中用
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
export default defineNuxtConfig({
vite: {
plugins: [
wasm(),
topLevelAwait()
],
},
});
现在我注释掉插件依然能用...依然属于踩坑了。
继续:
import { get_svg } from '~/utils/wasm/png-to-svg-wasm';
然后在调用get_svg时报错:
png_to_svg_wasm.js:52 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '__wbindgen_add_to_stack_pointer')
at get_svg (png_to_svg_wasm.js:52:14)
at reader.onloadend (PngToSVG.vue:67:13)
我真的会谢!!!
查阅资料中...
其实是因为需要先初始化一下,代码里面其实看得到,文档里也有说明(忘记在哪里了,反正后来找到了)
import init, { get_svg } from '~/utils/wasm/png-to-svg-wasm';
onMounted(() => {
init();
})
这样在调用get_svg
时才不会报错,但是为了保证我们的nuxt3项目的entry-xxx.js尽可能小一些,所以要使用动态加载:
const init = (await import("png-to-svg-wasm")).default;
const get_svg = (await import("png-to-svg-wasm")).get_svg;
开发调试:
yarn dev
没有问题,完美,打包。
yarn build
预览:
yarn preview
嘿嘿,不出意外的话就出意外了 ):
Cannot find package '/Volumes/D/ProjectDemo/nuxt-app/node_modules/png-to-svg-wasm/' imported from /Volumes/D/ProjectDemo/nuxt-app/.output/server/chunks/app/_nuxt/PngToSVG.ada0df9c.mjs
// console
Listening http://[::]:3000
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '/Volumes/D/ProjectDemo/nuxt-app/node_modules/png-to-svg-wasm/' imported from /Volumes/D/ProjectDemo/nuxt-app/.output/server/chunks/app/_nuxt/PngToSVG.ada0df9c.mjs
at new NodeError (node:internal/errors:371:5)
at legacyMainResolve (node:internal/modules/esm/resolve:335:9)
at packageResolve (node:internal/modules/esm/resolve:877:14)
at moduleResolve (node:internal/modules/esm/resolve:929:18)
at defaultResolve (node:internal/modules/esm/resolve:1044:11)
笑死,我明明可以运行的!!! 哪里出了问题呢??? 我已经数不清楚浏览过多少issue了,直接放解决方案吧:
nuxt.config.ts中添加:
build: {
transpile: [
'png-to-svg-wasm'
]
}
vite: {
optimizeDeps: {
exclude: [
'png-to-svg-wasm'
]
}
}
其中一个方案来源: https://github.com/nuxt/framework/issues/7206
谢天谢地,终于成功了!!! 头秃...
评论
扫描二维码获取文章详情
更多精彩内容尽在:WWW.ZNGG.NET