1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > js base64编码_使用psd.js将PSD转成SVG -- 基础篇(文字amp;图片)

js base64编码_使用psd.js将PSD转成SVG -- 基础篇(文字amp;图片)

时间:2024-06-02 18:33:37

相关推荐

js base64编码_使用psd.js将PSD转成SVG -- 基础篇(文字amp;图片)

背景

随着发展,活动会场页面的题图运营需要线上模板化,而自研的导购素材制作平台接入了海棠-创意中心,通过平台能力,将素材模板化,并且通过配置化的方式生成多种场景化,个性化的素材。但是创意中心的素材模板是基于SVG的,而会场页面的题图基本是基于Photoshop(PS)输出,源文件是PSD。由于SVG是面向矢量图形的标记语言,而PS是以位图处理为中心的图像处理软件,大多时候,PS无法直接导出SVG文件。

为了能让会场页面的题图模板接入到导购素材制作平台,同时降低设计师的使用门槛,我们需要在导购素材制作平台中实现直接将PSD转成SVG的功能,在线化的将PSD转成SVG,然后导入到创意中心,将题图模板化。

使用psd.js解析PSD

PSD文档是一个二进制文件,官网有相关的格式说明文档。目前,在前端解析PSD文档比较成熟的方案是使用 psd.js ,使用方法和API可以参见这里。

PSD的JSON结构

可以通过如下的方式导出PSD文档的JSON结构。

const PSD = require('psd');const psd = PSD.fromFile(file); // file为psd文档的存储路径psd.parse();console.log(psd.tree().export());

JSON结构大概如下:

{children: [ {type: 'group',visible: false,opacity: 1,blendingMode: 'normal',name: 'Version D',left: 0,right: 900,top: 0,bottom: 600,height: 600,width: 900,children: [ {type: 'layer',visible: true,opacity: 1,blendingMode: 'normal',name: 'Make a change and save.',left: 275,right: 636,top: 435,bottom: 466,height: 31,width: 361,mask: {},text: {value: 'Make a change and save.',font: {name: 'HelveticaNeue-Light',sizes: [ 33 ],colors: [ [ 85, 96, 110, 255 ] ],alignment: [ 'center' ] },left: 0,top: 0,right: 0,bottom: 0,transform: {xx: 1, xy: 0, yx: 0, yy: 1, tx: 456, ty: 459 } },image: {} } ] } ],document: {width: 900,height: 600,resources: {layerComps: [ {id: 692243163, name: 'Version A', capturedInfo: 1 },{id: 725235304, name: 'Version B', capturedInfo: 1 },{id: 730932877, name: 'Version C', capturedInfo: 1 } ],guides: [],slices: [] } } }

最外层的children描述的是图层信息,是一个数组类型的对象,document描述的是PSD文档全局信息,除了widthheight其他不必过多关注,我们需要重点关注的是children下的图层信息。

psd.js也支持导出单个图层的JSON。

// 访问图层节点const node = psd.tree().childrenAtPath('Version A/Matte')[0];// orconst node =psd.tree().childrenAtPath(['Version A', 'Matte'])[0];// 获取图层信息node.export();

JSON结构字段说明

图层信息都记录在children数组中,数组的item包含一些公共字段,比如typenamevisibletopbottomleftright等,同时,也根据不同的图层类型,存在一些特殊字段,比如文字图层,会用text字段记录文字相关的信息,比如字体、大小、颜色、对齐方式等。下面简单介绍一些重要的字段。

| 字段 | 说明 | | ---- | ---- | | type | 图层类型,group表示分组,layer表示普通图层 | | name | 图层名称 | | visible | 是否可见 | | opacity | 透明层,0~1 | | blendingMode | 图层模式 | | width/height | 图层内容的宽和高 | | top/bottom/left/top | 图层内容相对文档的范围区域 | | mask | 蒙层路径信息 | | image | 图层图像信息 |

获取图层的具体信息

通过export方法导出的JSON数据并非图层的所有信息,如果要获取一些具体信息,比如路径节点、渐变、描边等,可以通过get方法获取。

const node = psd.tree().childrenAtPath(['Version A', 'Matte'])[0];// 获取图层的路径const vectorMask = node.get('vectorMask');vectorMask.parse();const data = vectorMask.export();const {paths = [] } = data;paths.forEach(path => {// 变量路径节点});

get方法的参数对应着需要获取的图像信息的类型,支持的类型可以参照这里

由于SVG自身特性的限制,我们不需要用到所有PSD图层的所有信息,只需要关注如下几个主要的即可。

| 信息类型 | 说明 | | ---- | ---- | | solidColor | 填充色 | | gradientFill | 渐变色 | | typeTool | 文字 | | vectorMask | 路径 | | vectorStroke | 描边 |

生成SVG文档根标签

SVG文档,最外层的标签是svg,记录文档的编码、命名空间、视口(viewBox)大小等。svg标签的内容可以通过psd.js导出的document信息生成

const generateSvg = function(doc, content) {const {width, height } = doc;return (`<?xml version="1.0" encoding="UTF-8"?><!-- generated by lst --><svg version="1.1"xmlns="/2000/svg"xmlns:xlink="/1999/xlink" x="0px" y="0px"viewBox="0 0 ${this.width} ${this.height}"enable-background="new 0 0 ${this.width} ${this.height}"xml:space="preserve">${content}</svg>`);}

处理图像图层

最基本的,将PSD图层已image的形式转成SVG。SVG通过image标签显示图片,支持内联和外链两种方式。

<svg width="200" height="200"xmlns="/2000/svg" xmlns:xlink="/1999/xlink"><!-- link --> <image xlink:href="/files/6457/mdn_logo_only_color.png" height="200" width="200"/><!-- base64 --><image xlink:href="data:image/png;base64,......" height="200" width="200"/></svg>

使用psd.js将图层转成png

const node = psd.tree().childrenAtPath(['图层1'])[0];const pngData = node.layer.image.toPng();

如何通过SVG显示图片知道了,如何用psd.js将PSD图层转成图片也知道,那么接下来要做的就是将psd.js转换的图片数据嵌到SVG文档中。

基于Base64编码内联

Base64编码内联的本质就是将Base64编码的内容以字符串的形式嵌到image标签的href属性中,所以,关键的步骤就是如何将图片的数据转换成Base64编码的字符串。

const toBase64 = function(image) {return new Promise((resolve, reject) => {const chunks = [];image.pack(); // [1]image.on('data', (chunk) => {chunks.push(chunk); // [2]});image.on('end', () => {resolve(`data:image/png;base64,${Buffer.concat(chunks).toString('base64')}`); // [3]});image.on('error', (err) => {reject(err);});});}const embedToImage = function(href, width, height) {return `<image xlink:href="${href}" width="${width}" height="${height}"/>`}const node = psd.tree().childrenAtPath(['图层1'])[0];const pngData = node.layer.image.toPng();toBase64(pngData).then(content => {const image = embedToImage(content, node.get('width'), node.get('height'));// ...});

关键步骤:

[1] 通过pack方法将图片数据转成stream对象[2] 基于stream的data事件,获取流数据[3] 通过Buffer将流数据转换成Base64字符串

基于CDN地址外链

内联的方式有个好处,就是图片的数据可以打包到SVG文档中,不依赖外部环境,但也有个弊端,就是会使SVG的体积变大。我们可以借助CDN,将图片先上传到CDN上,拿到CDN地址后,通过外链的方式将图片嵌到SVG文档中。 对于集团,我们可以借助TPS平台,将图片上传到集团的CDN。

const Tps = require('@ali/tps-node');const path = require('path');const fs = require('fs');const toLink = function(image) {return new Promise((resolve, reject) => {const name = `tmp_png_${new Date().getTime()}.png`;const fileName = path.resolve('temp', name);image.pack().pipe(fs.createWriteStream(fileName)) // [1].on('finish', () => {resolve(fileName);}).on('error', (err) => {reject(err);});}).then(fileName => {// [2]const tps = new Tps({accesstoken: 'xxxxxxxxxx'});return tps.upload(fileName, {empId: 123456,nick: '花名',folder: 'ps-to-svg'}).then(({url }) => {fs.unlinkSync(fileName);return url;});}).then((url) => {return url;});}const embedToImage = function(href, width, height) {return `<image xlink:href="${href}" width="${width}" height="${height}"/>`}const node = psd.tree().childrenAtPath(['图层1'])[0];const pngData = node.layer.image.toPng();toLink(pngData).then(link => {const image = embedToImage(link, node.get('width'), node.get('height'));// ...});

关键步骤在于:

[1] 将文件缓存在本地[2] 上传到CDN,拿到地址

处理文字

另外一个会经常遇到的场景,就是处理PSD中的文字图层。SVG支持用texttspan来显示文字,具体用法可以参加这里(text)和这里(tspan)

基本的处理

最基本的,通过图层JSON对象的text字段获取到文字的内容、字体族名称、大小、颜色、对齐方式以及定位。

| 字段 | 说明 | | ---- | ---- | | value | 内容 | | font | 字体属性 | | font.name | 字体族名称 | | font.sizes | 字体大小 | | font.colors | 字体颜色 | | font.alignment | 对齐方式,centerleft或者right| | top/bottom/left/right | 文字的区域 | | transform | 文字的变形矩阵 |

需要注意的是,字体大小和颜色是一个数组,这是因为PS支持对一段文本中的其中一部分文字编辑字体样式,从而导致一段文本中,可能存在多个字体大小,或者字体颜色,因此字体的大小和颜色是用数组来记录的。

复杂的情况暂且放一放,这里我们先介绍比较简单的处理方式,使用SVG的text标签显示PSD字体图层。

拿到图层的文字信息数据后,将字段的值与svg标签的属性做一个映射即可,映射关系如下

| PSD字段 | SVG标签属性 | | ---- | ---- | | value | 标签内容 | | font.name | font-family | | font.sizes[0] | font-size | | font.colors[0] | fill | | font.alignment | text-anchor | | left | x | | top + font.sizes[0] | y | | transform | transform |

因此,转换的逻辑大概如下:

const toHex = (n) => {return parseInt(n, 10).toString(16).padStart(2, '0');};const toHexColor = (c = []) => {if (typeof c === 'string') {return c;}const [ r = 0, g = 0, b = 0 ] = c;return `#${toHex(r)}${toHex(g)}${toHex(b)}`;};const embedToText = function(text) {const {value, font, left, top } = text;const {name, sizes, leadings, colors } = font;return `<text x="${left}" y="${top + leadings[0]}" style="font-family: ${name}; font-size: ${sizes[0]}px" fill="${toHexColor(colors[0])}">${value}</text>`;}const node = psd.tree().childrenAtPath(['文字1'])[0];const text = embedToText(node.export().text);console.log(text);// <text x="60" y="20" style="font-family: FZChaoCuHei-M10; font-size: 14px" fill="#DF2211">阿里巴巴零售通</text>

有几个地方我认为需要注意的:字体颜色,大小,字体族以及文字的定位。

字体颜色

通过代码我们可以看到,colors字段取到的颜色的值,不是十六进制的RGB颜色值,取到的值是一个数组,例如

const color = text.font.colors[0];console.log(color); // [ 85, 96, 110, 255 ]

数组由四个整数组成,分别对应着颜色通道的Red、Green、Blue、Alpha,因此需要做一个转换。在PSD中,颜色的值基本都是按颜色通道分开存储。那么Alpha怎么处理呢?可以使用svg标签的fill-opacity属性进行处理。

计算大小

我在处理字体大小是,花了一些时间和功夫。

首先,PS的字体支持多种单位,最基础的pt,还有px,还有in等等,但是通过psd.js获取到的PSD信息,没法获取字体大小的单位,因此,我们需要做一个简化,比如要求设计师统一使用px,因此当我们获取到字体大小的值时,一律作为px来计算。

然后,有的PSD文档,取到的图层字体大小,并非是一个整数,而是一个很小的小数,但是在PSD中,显示却是正常的大小,这是为什么呢?

不知道,可能是不同版本的PS导致的问题吧。

如上图,右边的文字明显比左边的大,但是它的字体大小值只有2.44,很奇怪。后来,在psd.js的issues找到了解法,参加这里。

因此,关于文字图层字体大小的计算实现如下:

const computeFontSize = function(node, defValue = 24) {const {text } = node.export();const typeTool = node.get('typeTool');typeTool.parse();const sizes = typeTool.sizes();let size;if (sizes && sizes[0]) {if (text.transform.yy !== 1) {size = Math.round((sizes[0] * text.transform.yy) * 100) * 0.01;} else {// transform.yy为1时,sizes[0]的值就是字体显示大小的值,不需要计算size = sizes[0];}} else {size = defValue; // 默认}}

字体族

获取字体族也是一个让我比较头疼的问题。即使文字图层可能只用了一种字体,但不知为何,有的版本导出的PSD中,font.name记录的并非对应的字体,导致导出的效果与PSD稿不一致。经过反复的尝(cai)试(keng),最终找到了一个比较靠谱的解决方案。

const resolveFontFamily = function(node) {const {text } = node.export();const typeTool = node.get('typeTool');const fontFamily = typeTool.engineData.ResourceDict.FontSet.filter(f => !f.Synthetic).map(f => f.Name);return fontFamily[0] ? fontFamily[0] : text.font.name;}

另外一个地方,PSD文档中文本图层记录的字体族,并非我们平时看到的Font Family,而是PostScript Name,关于如何获取字体信息,推荐一个网站,opentype.js,上传字体后,可以看到字体的详细信息,以及符号集。

而opentype.js也是一个可靠强大的字体包解析工具库,亲测好使,墙裂推荐。

定位

关于文字图层的定位,SVG的text标签提供两种方式。

xy属性transform属性

而psd.js导出的PSD数据中,可以通过相应的字段,计算得到。

x = text.left;y = text.top + leading;transform = text.transform;

经(you)过(shi)实(cai)践(keng),我推荐使用transform进行文字定位。实现如下:

const resolveTransformAttrValue = function(text) {const {transform } = text;return `matrix(1 0 0 1 ${transform.tx} ${transform.ty})`;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。