先看几张PSD海报
《中秋节祝福海报》
PSD转H5演示视频
需求场景
- 中秋节将近,某点评App,需要在其App内部做一个活动页面,向用户宣传一下中秋节的一些活动
- 教师节将近,某校园App,需要在其App上做一个活动页面,大致的意思是 希望表达对老师的崇敬之情
- 最近一段时间,房地产市场火爆,某房产中介正在招兵买马,希望在其App上或者朋友圈发一个招聘H5
以前的模式
- 设计师同学用 Photoshop 做好了类似下图的一张 PSD,然后再使用SKetch 进行标注。准备完毕之后,将相关的文件发给前端同学。
- 前端同学拿到 Sketch 和 PSD 之后,开始按照标注进行制作H5页面,经过一到两天的静态页面开发(具体情况具体分析,具体的开发周期取决于页面的复杂度),期间也和设计师、产品经理同学进行了协商,最终还原了设计稿,双方都觉得比较满意,觉得可以上线了。
- 前端同学 把图片 + 代码 上传到服务器,配置一个域名之后,正式上线。大家就可以通过 h5.xxx.com 访问到一个中秋节H5页面了
现在 在鲁班H5中,这么做就可以了:
- 设计师同学只要打开鲁班的编辑器
- 点击图中的
Ps
(导入PSD) - 在弹出的页面中,上传需要制作成H5的PSD 文件即可
- 大部分情况下,不到一分钟,一个和 PSD 长得几乎一样的 H5 页面就会出现在中间画布上了(具体时间取决于psd 文件大小和图层多少)
- 点击预览,设计师再做一些细节的调整,点击发布,会得到一个 该H5作品对应的网址,制作完成!
- 我们发现,上述流程比之前工程师写代码的流程差不多节约了 三到四天的时间
实现原理
基础认知
- 我们的目的是:PSD -> H5
根据我们之前的讲解,以《中秋节祝福海报》为例,我们知道一个简单的H5本质上对应的是一个 JSON,如下所述(其中:lbp意思是 lu-ban-plugin):
[
{
name: '兔子图片',
type: 'lbp-image'
commonStyle: {
top: '100px',
left: '120px'
}
},
{
name: '英文祝福文字',
type: 'lbp-text'
text: 'Dear Friends',
commonStyle: {
top: '240px',
left: '160px'
}
}
]
- 那么
PSD -> H5
也就意味着,我们需要把 PSD 文件转换为上面的JSON结构,我们大概看下: - 假如我们有某种办法,能够得到PSD中的图层结构,能够对 PSD 文件做以下操作:
- 把每个图层中的图片保存下来
- 获得该图层的
左上角
相对于整个PSD 的相对左上角
的位置(以左上角为坐标原点,向右为X正轴,向下为Y正轴),也就是:{left, top} - 获得文字图层中的文字内容、文字大小、颜色、字体等信息
- 再高级点:图层的渐变色等
- 如果上面的办法可行,我们可以把 PSD 文件也抽象成一个 JSON(如下)
[
{
name: '兔子图片图层',
type: 'image',
position: {
top: '100px',
left: '120px'
},
},
{
name: '左灯笼图片图层',
type: 'image',
position: {
top: '100px',
left: '120px'
},
},
{
name: '右灯笼图片图层',
type: 'image',
position: {
top: '100px',
left: '120px'
},
},
{
name: '英文祝福文字图层',
type: 'text',
text: 'Dear Friend',
position: {
top: '240px',
left: '160px'
},
}
]
殊途同归
可以发现,PSD 文件的数据结构和鲁班中的一个作品的数据结构是非常相似的
我们来整理一下二者之间相似之处(如下),是不是发现PSD 的结构和鲁班在很多地方是可以一一对应的上的呢?
image(psd) <-> lbp-picture(luban)
text (psd) <-> lbp-text(luban)
实现路径
- 那么让我们看一下我们之前的假设:
假设我们可以分析PSD的结构,并从中得到图片、文字等图层信息
- 现在可以告诉大家了,上面的假设,我们完全可以实现。
- 首先,让我们重新认识一下PSD这个文件格式,通常情况下,我们都知道它是Photoshop导出的文件格式。其实它也是一个有着自己独特风格的文件格式。
- 我们先来看一份东西: PSD 的文件规范,访问地址:https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
看一下 PSD文件结构,有一种被计算机网络七层结构支配的恐惧😂
也就是说 PSD 文件是由几个部分组成的:
- File Header
- Color
- Image Resources
- Layer And Mask Information
- Image Data
我们借助 psd.js 来尝试下对 psd 文件进行一些分析,代码如下,可以copy 运行(前提,需要有一个 psd 文件,拷贝到该脚本的相同目录中) ```javascript
mkdir demo
将 psd 文件拷贝到 demo 文件夹中
yarn add psd
var path = require(‘path’) var PSD = require(‘psd’); var psd = PSD.fromFile(path.resolve(__dirname, ‘your_psd_file_name.psd’)); psd.parse();
就可以看到如下的数据结构了
console.log(psd.tree());
```javascript
root {
psd: PSD {
file: File {
data:<Buffer 38 4....> ,
pos: 7577696
},
parsed: true,
header: Header {
file: [File],
sig: '8BPS',
version: 1,
channels: 4,
rows: 900,
cols: 600,
depth: 8,
mode: 3
},
resources: LazyExecute {
obj: [Resources],
file: [File],
startPos: 30,
loaded: false,
loadMethod: 'parse',
loadArgs: [],
passthru: []
},
layerMask: LazyExecute {
obj: [LayerMask],
file: [File],
startPos: 28884,
loaded: true,
loadMethod: 'parse',
loadArgs: [],
passthru: []
},
image: LazyExecute {
obj: [Image],
file: [File],
startPos: 7577696,
loaded: false,
loadMethod: 'parse',
loadArgs: [],
passthru: []
}
},
layer: {
name: null,
left: 0,
right: 600,
top: 0,
bottom: 900,
height: null,
width: null,
node: [Circular]
},
parent: null,
_children: [Group {
layer: [Layer],
parent: [Circular],
_children: [Array],
name: '站长素材(sc.chinaz.com)',
forceVisible: null,
coords: [Object],
topOffset: 0,
leftOffset: 0
}
],
name: null,
forceVisible: null,
coords: {
top: 0,
bottom: 900,
left: 0,
right: 600
},
topOffset: 0,
leftOffset: 0
}
我们来对上面的数据结构进行一些简单的分析,重点看一下其中的 root.psd
,也就是下面的数据结构。
其中 file:File 类似 TypeScript 的写法,表示 file 变脸的类型是 File。LazyExecute 类似于迭代器的感觉,用的时候,执行一下才能得到结果,也就是 realResource = root.psd.resource()
类似这种感觉
{
file: File {
data:<Buffer 38... >,
pos: 7577696
},
parsed: true,
header: Header {
file: [File],
sig: '8BPS',
version: 1,
channels: 4,
rows: 900,
cols: 600,
depth: 8,
mode: 3
},
resources: LazyExecute {
obj: [Resources],
file: [File],
startPos: 30,
loaded: false,
loadMethod: 'parse',
loadArgs: [],
passthru: []
},
layerMask: LazyExecute {
obj: [LayerMask],
file: [File],
startPos: 28884,
loaded: true,
loadMethod: 'parse',
loadArgs: [],
passthru: []
},
image: LazyExecute {
obj: [Image],
file: [File],
startPos: 7577696,
loaded: false,
loadMethod: 'parse',
loadArgs: [],
passthru: []
}
}
我们对其进行进一步的整理,发现它其实可以整理为下面的这种结构
root.psd: {
file<File>: {},
parsed: true,
header<Header> : {},
resources<LazyExecute>: {},
layerMask<LazyExecute>: {},
image<LazyExecute>: {}
}
我们仔细看一下,上面的数据结构 是不是和 PSD 文件规范
里面描述的 Photoshop file Structure
能够一一匹配上了。
到这里相信大家应该能够明白了吧,我们通过程序(主要借助 psd.js)发现可以对 PSD 文件进行解读,从中得到我们想要的数据。接下来,我们对 Photoshop file Structure
进行略微深入一些的解读
Photoshop file Structure 解读
- 在这里,我就不班门弄斧了,大家感兴趣的话,可以看看这位小哥iamgqb 翻译的PSD 文件格式规范/Adobe Photoshop File Formats Specification. 再次表示感谢
- 我会补充一些说明,也就是对规范中的一些字段在实际有何用途
1. File Header: 文件头段包括图像的基本属性
大家可以先读一下 译文,以下内容大部分来自译文。 补充部分,为我个人理解
长度 描述 补充 4 Signature(签名): 总是等于 ‘8BPS’ 。 不要去读取签名不符合这个值的文件。 用户在上传文件的时候,可以根据 签名 判断该文件是否是合法的PSD文件 2 Version(版本): 总是等于 1。不要去读取版本不符合这个值的文件。 (PSB 版本是 2 。) 6 Reserved(保留字段): 总是 0 。 2 图像通道数量,包括透明通道,支持的范围从 1 到 56 。 4 像素表示的高度,支持的范围从 1 到 30,000 。 (PSB 最多 300,000.) 4 像素表示的宽度, 支持的范围从 1 到 30,000 。 (PSB* 最多 300,000) 2 Depth(深度): 每个通道使用的位数,支持的值包括 1, 8, 16, 32 。 2 文件的色彩模式。支持的值包括:Bitmap = 0; Grayscale = 1; Indexed = 2; RGB = 3; CMYK = 4; Multichannel = 7; Duotone = 8; Lab = 9