运营部门会做大量的活动,产生海量的图片,这些活动图片理论上是可以复用的,因为一些类似的活动往往只需要替换 logo,更改文字等等。但对于运营人员来说这些并不能复用,必须找到设计人员重新修改,这就无形中增加了沟通成本。尽管对于设计人员来说 psd 可以复用,而且简单,但是这样的小需求多如牛毛,设计师被这种简单切重复任务干扰至疲惫不堪,无法专心设计。
为了减少设计工作量,我们想到,降低作图门槛,让运营人员参与其中,运营可以自己通过平台生成资源。让图片平台化,服务化。
这样,设计人员可以专注设计好模板,上传至平台,运营人员使用模板稍作修改即可生成精美的活动图片。既解决了设计人员工作量的问题又解决了沟通成本问题,大大加减小活动上线的周期。
所以打造一款这样的产品我们势在必行。
打造一款操作简单、易于上手的图片编辑器,同时能统一管理运营活动资源,轻松复用。
随心图能将 psd 文件转换成,随心图识别的格式,然后用户可以在工作台随意编辑,编辑好的内容可以在该平台保存,管理,分享,复用。
我们先看一下功能演示
功能演示
PSD模板
,随心图模板
,图片素材
,PSD文字素材
,设计库
,工作台
,组合
PSD模板
:由 ui 人员设计的 psd 文件,用于导入随心图系统最终生成随心图模板
。
随心图模板
:由PSD模板
转换成的随心图系统可识别的模板,可在工作台
自由编辑。
图片素材
:普通图片,可添加到工作台,随意组合最终保存生成用户的设计
。
组合
:由多个图片或文字组合一起,可进行整体操作。
PSD文字素材
:由 psd 文件导入转换成随心图系统能识别的组合
。
工作台
:随心图系统核心,用于模板
,素材
,组合
的动态编辑。
模板库管理
主要管理 psd 模板,设计好的 psd 通过这里上传,通过我们解析成 fabric 识别的 json 对象,然后导入到我们的工作台,这是用户可对其进行编辑微调,点击保存最终生成平台可识别的随心图模板。
素材库管理
管理图片素材
,PSD文字素材
,方便设计时引用。
模板中心
创建设计的入口用户可直接应用随心图模板
,也可自定义设计。
设计库
管理用户发布过的设计
,便于设计
分享。
我的主页
管理的是用户自己的设计
,操作方式和设计库
类似。
Canvas
,Fabric.js
,PSD.js
,Vue
,Element
Fabric.js
: 是一个使 canvas 变得更容易的框架,用它绘制出来的图形元素可实现动态交互。
PSD.js
:可解析 psd 文件成元素的描述对象 json 格式,类似于虚拟 dom 树。
核心原理概括
导入:将 psd 文件转换成描述对象,再通过 Fabric.js 加载展示到页面上,用户通过操作图形元素。
保存:Fabric.js 将图形转换成 json 字符串,写入数据库。
加载:数据库获取 json 字符串,由 Fabric.js 加载展示图形。
工作台组成
工作台由 4 个区域组成
头部
:包含一些功能按钮,保存
,下载图标
等
素材资源区
:包括图片资源,文字资源,模板资源
画板
:工作台核心,可交互式图形展示,可实现移动,缩放,剪切等等一系列操作
控制区
:控制画板中已选中的元素,如:更改透明度,字体大小等
工作台布局代码
<layout>
<template v-slot:header>
<layout-header ref="header" />
</template>
<template v-slot:aside>
<sidebar />
</template>
<template v-slot:canvas>
<draw-board />
</template>
<template v-slot:controller>
<controller />
</template>
</layout>
我们来看一下工作台图片
画板
画板是一个功能比较复杂 Vue 组件,如
画布缩放
,替换图片
,设置透明度
等等功能封装成一个个函数暴露个外界使用。
画板组件文件夹结构
├── draw-board
│ ├── components
│ │ ├── preview
│ │ │ └── index.vue # 用于生成静态canvas(非交互式的),如:画布预览
│ │ └── zoompanel
│ │ └── index.vue # 缩放控制器 - 100% +
.
.
.
│ ├── index.vue # 主文件
│ ├── mixins # mixins 按功能划分的封装好的API
│ │ ├── base.js
│ │ ├── controller.js
│ │ ├── psd.js
│ │ └── utils.js
.
.
.
mixins 规范
因为功能很多所以采用 mixins 来分块组织代码,每一类功能创建一个 mixin 文件。
mixin 文件结构
mixins/group.js
/*
* 由于mixins的问题就是查找来源很难, 所以我们在创建mixin方法时, 规定在方法前面加上功能前缀。
* 如:group_group()
* | |
* 组合相关 组合
* 这样我们在其他地方引用时就很容易找到改方法在哪里定义的。
*/
export default {
methods: {
/**
* 将选择的元素打包成group
*/
group_group() {
.
.
.
},
/**
* 将选择的group拆分成单个元素
*/
group_ungroup() {
.
.
.
},
},
};
画板与外部组件通信
当画板渲染完成会发出一个 setup 事件,然后父组件监听该事件,事件被触发时将 画板的实例对象挂载到
Vue.prototype.$fabricBoard
这样所以组件可以通过this.$fabricBoard.
来调用画板 API 了
当然这种方式必须要等工作台组件渲染完毕,其他组件才能调用,至于其他组件如何知道画板是否渲染完毕,下面的代码中有写到
画板组件
export default {
name: 'drawBoard',
mounted() {
this.init();
},
methods: {
async init() {
.
.
.
this.$emit('setup', this); // 工作台组件渲染完成
},
},
};
父容器(工作台)组件
<template>
<layout>
.
.
.
<template v-slot:canvas>
<draw-board @setup="handleBrawBoardSetup"/>
</template>
</layout>
</template>
<script>
import Vue from 'vue';
import Layout from './components/layout';
import DrawBoard from './components/draw-board';
export default {
components: {
Layout,
DrawBoard,
},
methods: {
handleBrawBoardSetup(fabricBoard) {
window.$fabricBoard = fabricBoard; // window 上挂载画板实例, 方便开发调试
window.$fabricCanvas = fabricBoard.canvas;
Vue.prototype.$fabricBoard = fabricBoard; // Vue.prototype 上挂载画板实例, 方便其他组件调用
Vue.prototype.$fabricCanvas = fabricBoard.canvas;
this.$store.commit('drawing/SET_FABRIC_BORAD_MOUNTED', true); // 将画板创建完毕状态 存入 vuex state 中, 其他组件检测到实例挂载完毕,才开始调用画板API
},
.
.
.
},
};
</script>
画板实例对象
这个截图只有部分,红色方框里就是工作台部分 API
画板与其他组件通信逻辑
其实开发中还踩了不少坑,如内嵌工作台画布,实现剪切,改写操作器,动态辅助线等等,有机会也会分享出来。
首先我们假设 psd 文件结构就是一些图层,图片,文字等,这些信息是可以通过某种转换变成我们能看懂的数据结构如:
{
"text": {
"left": 20,
"top": 20,
"value": "hello"
}
}
只要是这样可读,我们就可以将相同意思的字段和我们 fabricjs 的数据结构一一对应,转换出一份 fabric 的数据结构。
我们来了看一下 psd 文件,一般我们用 Photoshop 导出的文件就是这种格式。这里有一份 PSD 文件规范
https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
既然有规范就证明我们的假设成立
于是我们找到一款 PSD 文件解析器,PSD.js,下面我们看一下解析源码和转换后的数据结构
this.fromURL = function(url) {
return new RSVP.Promise(function(resolve, reject) {
var xhr;
xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onload = function() {
var data, psd;
data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
psd = new PSD(data);
psd.parse();
return resolve(psd);
};
return xhr.send(null);
});
var psd;
PSD.fromURL("https://xxxxx/template/xxx.psd").then(data =>{
psd = data
})
该代码核心就是psd.parse();
将 psd 文件格式化成 js 对象,我们来看一下 parse 后的 psd
是不是跟 Adobe Photoshop File Formats Specification
图片里的结构很像,到了这一步我们的需求基本实现,
我们再看一下由 psd.js 提供的导出数据接口psd.tree().export()
这样我们可以从中找到我们需要的信息与 fabric.js 数据结构的字段了。
最后我们将转换好的数据用 fabric.js 加载就能展示出 canvas 图形了。
其实有了 psd 这些属性信息,我们还可以做很多事情,如 psd 转 h5 页面等等。
首先我们看一下 PSD.js 这段源码
xhr.responseType = 'arraybuffer';
.
.
.
data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
他将请求的 psd 资源的 arraybuffer 转换成 Uint8Array
大家一定好奇将 Uint8Array 转换成 String 会是啥样,我们来试一下
能看出一点像 xml 格式,但这只能看个大概,会有乱码。至少证明通过某种规范是可以读到 psd 信息的。
接下来我们结合Adobe Photoshop File Formats Specification
规范来看,上面图中我们看到 photoshop 文件结构分为
File header
Color mode data
Image resources
Layer and mask information
Image data
这 5 个部,我们尝试解析 header 信息,看下规范原文
文中已说明表格中 length 表示字节数,意思是说 字段在 Uint8Array 中所占的长度,按照表格从上往下开始解析,我们从 index 0 开始时
表格第 1 行 length = 4 我们取数组前 4 项
psd.file.data.slice(0, 4);
// Uint8Array(4)[(56, 66, 80, 83)];
转换成 String, 返回 8BPS
正如描述所说我们获取到了 Signature
String.fromCharCode(...psd.file.data.slice(0, 4));
// "8BPS"
表格第 2 行 length = 2 我们应取数组前 4-6 项
psd.file.data.slice(0, 4);
// Uint8Array(2)[(0, 1)];
这是 signed short 类型我们不能使用 fromCharCode
进行解析,我们要借助 jspack
库来解析,该库可以将 Uint8Array 解压出传入类型对应的值
// '>h' format 表示返回类型 signed short
jspack.Unpack('>h', [0, 1]);
// [1]
这样我们得到 Version = 1, 正如他的描述 Version: always equal to 1. Do not try to read the file if the version does not match this value. (**PSB** version is 2.)
后面的就不一一演示来,我们贴一段 PSD.js 源码来看,这段代码正好对应这个 header 这张表格
parse: ->
@sig = @file.readString(4)
if @sig != '8BPS'
throw new Error('Invalid file signature detected. Got: '+@sig+'. Expected 8BPS.')
@version = @file.readUShort()
@file.seek 6, true
@channels = @file.readUShort()
@rows = @height = @file.readUInt()
@cols = @width = @file.readUInt()
@depth = @file.readUShort()
@mode = @file.readUShort()
colorDataLen = @file.readUInt()
@file.seek colorDataLen, true
最后我使用他的 API 来获取一下头部信息
解析原理大概是这样,但要全部解析出来确实工程浩大而且要对 psd 属性非常属性,感谢大神们的开源
文字支持边框,阴影,图片背景,渐变背景等等
支持各种滤镜效果
支持不同的模板文件,如:sketch
支持移动端编辑
图片下载支持压缩,支持下载每个图片元素(切图功能)
目前我们一路踩坑填坑完成从 0 到 1,当然还存在不足,希望得到大家宝贵意见。
我们相信不断的打磨,随心图一定可以成为提升效率的利器。
---------- END ----------
加入掌门
掌门在招职位有研发总监( Java
/音视频方向)、研发工程师/架构师( Web
前端/ Java
/ iOS
/ 安卓 )、测试工程师(功能/自动化/性能)、DBA
、大数据工程师、算法工程师( NLP
/用户画像)、K8S
架构师、运维工程师、产品经理。欢迎加入掌门教育大家庭,一起畅谈技术,分享交流。
投递信箱:zeying.shi@zhangmen.com 施老师。
往期好文