React + Fabric + ImageMagick 实现大图片DIY个性化定制
一,需求背景:
某个印刷公司,有一系列的设计文件模板。接到客户订单时,就在这些设计文件模板上,做一些简单的定制,就能够满足客户的印刷需求。 如在设计文件模板上添加客户的Logo,二维码,联系方式等。
1,面临困境:
a,每天有上千个模板文件需要加Logo,文字。印刷公司不得不请几个设计师来完成这项工作。
b,设计师要不断的与客户沟通,如文字颜色,字体,文字大小,二维码, Logo的位置。
c,设计文件不能统一归档存储,时间久了容易丢失。
d,工作枯燥泛味(在模板文件上更换Logo,添加文字)。
2,解决方案:
a,公司决定开发一个网站,公布设计文件模板,让客户挑选心仪的文件模板。
b,让客户自己上传 Logo,二维码,添加文字等信息。
c,系统自动保存客户的设计文件,并与销售订单自动关联。
二,需求转换为软件原型
1,图片需求:
a,可以在工具栏中,往设计图片上添加Logo,二维码等图片信息
b,对上传的图片要进行移动,放大,缩小,旋转
c,可以预览,删除上传的图片
2,文字需求:
a,可以添加与定义文字信息
b,文字大小,字体,颜色可自定义
c,文件可以移动,旋转等功能
d,可以删除文字。
3,软件原型:
三,技术选型与实现
1,技术选型:
a,印刷行业的图片都非常大,小则几M,大则几十M,上百M。在网页上只能操作缩略图,然后再生成原图。
b,要对图片,文字进行移动,旋转,放大缩小,我选择了Fabric类库。
c,前端框架主要有3种Vue.js ,React ,Angular 。对我个人来说Vue.js是我最熟悉的框架之一,但是 Vue.js + Fabric 的组合没有 React + Fabric 成熟。最终我选择了 React + Fabric的组合。
d,把设计好的图片,文字信息以Json的数据推送到服务器,通过ImageMagick 技术生成印刷原图。
2,代码实现:
前端关键代码:
a,图片的移动,放大缩小,旋转实现:
img.on('selected',(e) => { }); img.on('moved',(e) => { if(e.target) { let selectObj = this.props.templateModel.rows.filter(d => {return d.uid === e.target.uid})[0]; if(selectObj) { selectObj.top = e.target.top; selectObj.left = e.target.left; this.UpdateItemByChild(selectObj); } } }) img.on('rotated',(e) => { if(e.target) { let selectObj = this.props.templateModel.rows.filter(d => {return d.uid === e.target.uid})[0]; if(selectObj) { selectObj.angle = e.target.angle; var rect = e.target.getBoundingRect(); selectObj.top = rect.top; selectObj.left = rect.left; selectObj.width = e.target.width; selectObj.height = e.target.height; this.UpdateItemByChild(selectObj); } } }) img.on('scaled',(e) => { if(e.target) { let selectObj = this.props.templateModel.rows.filter(d => {return d.uid === e.target.uid})[0]; if(selectObj) { if(e.target.scaleX) { selectObj.scaleX = e.target.scaleX; } if(e.target.scaleY) { selectObj.scaleY = e.target.scaleY; } selectObj.width = e.target.width; selectObj.height = e.target.height; selectObj.top = e.target.top; selectObj.left = e.target.left; this.UpdateItemByChild(selectObj); } } });
b,图片在称动,放大缩小,旋转时的位置与大小约束:
img.on('rotating',(e) => { }); img.on('scaling',(e) => { var maxWidth = fixedInfo.width; var maxHeight = fixedInfo.height; if(e.transform.action === 'scaleX' || e.transform.action === 'scaleY' || e.transform.action === 'scale'){ if(img.width * img.scaleX >= maxWidth){ img.scaleX = maxWidth / img.width; } if(img.height * img.scaleY >= maxHeight){ img.scaleY = maxHeight / img.height; } } }); img.on('moving',(e) => { if(fixedInfo){ if(img.left <= fixedInfo.left || img.top <= fixedInfo.left){ img.setCoords(); img.left = Math.max(img.left, fixedInfo.left); img.top = Math.max(img.top, fixedInfo.top); } let maxLeft = fixedInfo.left + fixedInfo.width - img.width * img.scaleX; let maxTop = fixedInfo.top + fixedInfo.height - img.height * img.scaleY; if(img.left >= maxLeft || img.top >= maxTop) { img.setCoords(); img.left = Math.min(img.left, maxLeft); img.top = Math.min(img.top, maxTop); } } });
c,文字的移动,放大缩小,旋转实现:
txt.on('moved',(e) => { if(e.target) { let selectObj = this.props.templateModel.rows.filter(d => {return d.uid === e.target.uid})[0]; if(selectObj) { selectObj.top = e.target.top; selectObj.left = e.target.left; this.UpdateItemByChild(selectObj); } } }) txt.on('rotated',(e) => { if(e.target) { let selectObj = this.props.templateModel.rows.filter(d => {return d.uid === e.target.uid})[0]; if(selectObj) { selectObj.angle = e.target.angle; selectObj.width = e.target.width; selectObj.height = e.target.height; selectObj.top = e.target.top; selectObj.left = e.target.left; this.UpdateItemByChild(selectObj); } } }) txt.on('scaled',(e) => { if(e.target) { let selectObj = this.props.templateModel.rows.filter(d => {return d.uid === e.target.uid})[0]; if(selectObj) { if(e.target.scaleX) { let tempFontSize = Math.ceil(e.target.fontSize * (e.target.scaleX / this.props.templateModel.scaleX)); e.target.fontSize = tempFontSize; selectObj.fontSize = tempFontSize; // X轴 Y轴是一样的缩放 e.target.scaleX = this.props.templateModel.scaleX; selectObj.scaleX = this.props.templateModel.scaleX; e.target.scaleY = this.props.templateModel.scaleY; selectObj.scaleY = this.props.templateModel.scaleY; selectObj.width = e.target.width; selectObj.height = e.target.height; selectObj.top = e.target.top; selectObj.left = e.target.left; this.UpdateItemByChild(selectObj); } this.UpdateItemByChild(selectObj); } } });
d,特别注意当文字大小改变时,字体大小需要随之变更
txt.on('scaled',(e) => { if(e.target) { let selectObj = this.props.templateModel.rows.filter(d => {return d.uid === e.target.uid})[0]; if(selectObj) { if(e.target.scaleX) { let tempFontSize = Math.ceil(e.target.fontSize * (e.target.scaleX / this.props.templateModel.scaleX)); e.target.fontSize = tempFontSize; selectObj.fontSize = tempFontSize; // X轴 Y轴是一样的缩放 e.target.scaleX = this.props.templateModel.scaleX; selectObj.scaleX = this.props.templateModel.scaleX; e.target.scaleY = this.props.templateModel.scaleY; selectObj.scaleY = this.props.templateModel.scaleY; selectObj.width = e.target.width; selectObj.height = e.target.height; selectObj.top = e.target.top; selectObj.left = e.target.left; this.UpdateItemByChild(selectObj); } this.UpdateItemByChild(selectObj); } } });
后端关键代码:
a,ImageMagick 把 文字合并在原图上:
convert -font "H:\Works\Coordinator\Coordinator.MvcWebAPI\ImageMagick\simsun.ttc" -fill #FE9200 -pointsize 450 -gravity northwest -annotate 28.0971789257395x28.0971789257395+4104.47275822051+437.86943092823 "文字定义" "H:\Works\Coordinator\Coordinator.MvcWebAPI\01.SourceFiles\diy\20210317\140907767-2e86273b978445359c5cdd9be9beef91.jpg" "H:\Works\Coordinator\Coordinator.MvcWebAPI\01.SourceFiles\diy\20210317\140907767-2e86273b978445359c5cdd9be9beef91.jpg"
b,ImageMagick 把附加图片合并在原图上:
convert "H:\Works\Coordinator\Coordinator.MvcWebAPI\01.SourceFiles\diy\20210317\140907767-2e86273b978445359c5cdd9be9beef91.jpg" ( -resize 1000x1000! "https://localhost:44300/01.SourceFiles/diy/20210317/-44a34290d3424b55a04a1f59b03f8e0d.png" -background transparent -rotate 0 ) -gravity northwest -geometry +6798.3525+363.902921615202 -composite "H:\Works\Coordinator\Coordinator.MvcWebAPI\01.SourceFiles\diy\20210317\140907767-2e86273b978445359c5cdd9be9beef91.jpg"
c,生成原图与预览图:
convert "H:\Works\Coordinator\Coordinator.MvcWebAPI\01.SourceFiles\diy\20210317\140907767-2e86273b978445359c5cdd9be9beef91.jpg" -resize 1000x "H:\Works\Coordinator\Coordinator.MvcWebAPI\01.SourceFiles\diy\20210317\140907767-2e86273b978445359c5cdd9be9beef91_s.jpg"
四,软件预览
有希望更深层次交流的朋友,请扫描博客头像的二维码
欢迎指正。