使用psd.js將PSD轉成SVG -- 基礎篇(文字&圖片)

轉載至微信公共號: 方凳雅集javascript

背景

隨着發展,活動會場頁面的題圖運營須要線上模板化,而自研的導購素材製做平臺接入了海棠-創意中心,經過平臺能力,將素材模板化,而且經過配置化的方式生成多種場景化,個性化的素材。可是創意中心的素材模板是基於SVG的,而會場頁面的題圖基本是基於Photoshop(PS)輸出,源文件是PSD。因爲SVG是面向矢量圖形的標記語言,而PS是以位圖處理爲中心的圖像處理軟件,大多時候,PS沒法直接導出SVG文件。html

爲了能讓會場頁面的題圖模板接入到導購素材製做平臺,同時下降設計師的使用門檻,咱們須要在導購素材製做平臺中實現直接將PSD轉成SVG的功能,在線化的將PSD轉成SVG,而後導入到創意中心,將題圖模板化。前端

使用psd.js解析PSD

PSD文檔是一個二進制文件,官網有相關的格式說明文檔。目前,在前端解析PSD文檔比較成熟的方案是使用 psd.js ,使用方法和API能夠參見這裏java

PSD的JSON結構

能夠經過以下的方式導出PSD文檔的JSON結構。node

const PSD = require('psd');
const psd = PSD.fromFile(file);  // file爲psd文檔的存儲路徑
psd.parse();
console.log(psd.tree().export());
複製代碼

JSON結構大概以下:git

{ 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下的圖層信息。github

psd.js也支持導出單個圖層的JSON。json

// 訪問圖層節點
const node = psd.tree().childrenAtPath('Version A/Matte')[0];
// or
const 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="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/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="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <!-- link --> 
  <image xlink:href="https://mdn.mozillademos.org/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})`;
}
複製代碼

爲啥?由於阿里媽媽的海棠-創意中心的SVG模板支持transform屬性定位文字。

相關文章
相關標籤/搜索