自动图片生成在前端开发中的一些尝试

图片处理在前端开发过程中占据了不少的时间,很是累人。在本文中我们不讨论如何提高切图的效率,我们讨论另一个问题:如何处理设计稿中的一些简单图形。不知道你又没有遇到过这种烦恼:“设计师给你的精致的PSD中有一个简单图形,就是那用用多边形、圆形和线条组成的图形。这个图形用css3实现不了,或者能实现,但为了兼容某些浏览器不能用css3来实现,只能切图。好的,你很快切完了并在样式中引用了。但没过多久需求上要求改下图片的颜色,而这是你已经将导出图片的那个PSD删除了,你还得重新在设计稿中把这个图形抠出来”。或者来一个更变态的版本,你做的项目支持换肤功能,在不同的皮肤下面这个图形会有不同的颜色。那么就需要你将这个图片导出为几个不同颜色的版本。如果后期有颜色的修改,那么你就需要重新一一修改并导出,如果这时用于导出图片的psd没有了,你就得重新从设计稿中扣出来,然后在一一修改后导出图片,稍不注意还可能造成两次图片的尺寸和位置不一样,造成错位。如果你没有遇到过上述情况,你应该是幸运的,而我两种都遇到过,在我做这些繁琐的修改的时候内心总是处于千万只羊驼奔跑的场景。

难道就没有一种办法来简化这中操作吗?能不能像修改css一样,只修改几个参数就完成对图片的修改呢?要完成这一目标我们必须完成两项工作:1. 如何用文本来描述这些简单图形 2. 如何将用文本描述的简单图形转换为图片。对于第一项工作比较好办, 用svg来完成就可以了,但第二项工作该如何实现呢?

如何将svg转换为图片

因为我们用SVG描述的图形一般情况下都需要和页面的其他元素融合在一起,所以必须将我们的SVG图形转换为支持透明的png格式。那么如何实现svg转png呢?在网上搜索了很久一直没有特别理想的。找到的解决方案基本就两个套路,要么用命令行调用phantom(也有通过canvas来实现的,但canvas的支持还是基于phantom),要么用命令行调用GraphicsMagick(简称gm)。而这两个软件在体积上都是巨无霸,安装起来非常繁琐尤其是在windows下面,对svg的支持也不是太友好。phantom需要借助canvas来实现SVG转png,太折腾。而gm需要安装第三方库才能处理svg,
而处理后的图片效果也不太好,我的测试图片的透明部分被填充了白色,透明都没处理好其他特性就没心情测试了,估计好不到那里去。

至此研究进入了死胡同,没能继续推进,直到有一个在调研另一个项目的某个小功能的技术可行性时才意外的有所突破。当时我想做一个小工具以实现将一个非常长的图片进行切割后上传,以便于在手机端进行lazyload. 因为知道nodejs处理图片方面比较渣,所以很识趣的没有使用nodejs来写而是改用了java。得益于java良好的生态系统很容易完成了我想要的功能。完成之后我不禁想,我这个功能为什么不用java来试试呢?在Google上搜索"java svg to png", 立即有了结果,Apache下有个专门处理svg的库batik。我原来有个很好的同事总跟我说“解决一个问题最难的是如何将自己的问题转换为合适的搜索关键词”。至此真是深有体会。

batik不仅对SVG的支持非常好,功能也非常强大,不仅能通过DOM API来操作SVG文档,而且还提供了将svg转换为其他格式、svg中嵌入js代码等很多非常实用的功能。官方的jar包中有一个可以在命令行中运行,可以完成将svg图片转换为其他格式,使用起来也非常简单:

java -jar batik-rasterizer.jar foo.svg

这恰好是我们需要的功能。至此基于batik的解决方案应该是目前为止最完美的,jar文件不需要单独安装就可以运行,而对目录结构也没有要求。完全可以把jar文件和js文件放到一个目录下然后和js代码一起发布。虽然也需要安装java运行环境,但java运行环境的安装相对来说很容易,而且公司每台电脑都安装了,所以这个条件是可以接受的。

导出可独立运行jar文件

batik自带的那个可以在命令行下运行的jar文件虽然能满足我们的但还是有两个地方存在不足。首先是这个jar文件不能独立运行,对项目中的其他jar文件有依赖,必须把它依赖的jar文件按特定的目录结构存放才能运行。这么多文件凌乱的放到一起,看着非常不爽。还有一点让人不爽的是这个jar文件只能通过命令行指定要操作的svg文件, 不支持通过命令行指定要转换的svg代码,所以你不能丢一个svg字符串让他处理,必须把字符串写到一个临时文件中。这很不方便和其他构建工具进行集成,而且而向代码目录中创建和删除文件可能会触发grunt的watch任务造成不必要的编译。基于这两个不能忍受的缺点,所以我们不能使用他自带的那个jar文件,需要自己编写一个可独立运行且支持通过命令行设置要转换的svg代码的jar文件。结合网上的资料整个功能很容易实现,下面是代码实现:

package myless.func;

import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.xerces.impl.dv.util.Base64;

public class Converter {
    public static void main(String... args) throws Exception{
        // 为了避免ms dos下蛋疼的编码问题,对参数做了base64编码
        byte[] svg_code  = Base64.decode(args[0]);
        String save_path = new String(Base64.decode(args[1]), "utf-8");

        if(args.length >= 3 && args[2].toLowerCase().equals("--show-debug")){
            System.out.println("svg content: \n" + new String(svg_code, "utf-8"));
            System.out.println("save path  : \n" + save_path);
        }   

        InputStream  svg_stream = new ByteArrayInputStream(svg_code);
        OutputStream png_stream = new FileOutputStream(save_path);
        TranscoderInput  input_image  = new TranscoderInput(svg_stream);
        TranscoderOutput output_image = new TranscoderOutput(png_stream); 

        PNGTranscoder transcoder = new PNGTranscoder();
        transcoder.transcode(input_image, output_image);

        svg_stream.close();
        png_stream.flush();
        png_stream.close();
    }
}

使用了eclipse的export功能完成了导出可执行jar文件,具体操作步骤可以参考百度经验的这篇文章

将转换功能集成到less

一旦jar文件能够独立运行就很容易被nodejs使用了,因为nodejs本身支持通过命令行调用系统命令。而且0.12.0版本之后添加了同步调用命令行的功能。因此我们可以这样实现用nodejs来完成将SVG图片转换为png图片的功能。

  • nodejs程序通过当前文件的路径计算出需要调用的jar文件路径
  • nodejs生成需要转换的SVG代码并base64编码
  • nodejs设置转换后的图片的保存路径并base64编码
  • nodejs以同步方式调用命令行:
java -jar convert.jar base64-encode-svg base64-encode-save-path

下面的代码是grunt-myless中的转换函数实现。因为转换的过程比较耗时,为了提高总体的编译进度做了判断是否需要重现转换的逻辑,只有svg代码的md5值发生了变化才会重新转换。

/**
 * @fileOverview 将SVG代码转换为png文件.
 * @param   String    转换后文件保存路径
 * @param   String... svg属性和svg代码
 * @return  String    保存绝对地址
 * @example
 *  div {
 *     background: svg-to-png(‘width=60px‘, ‘height=30px‘, ‘baseb4-encode=true‘, <<<EOF
 *        <!-- add you svg code here-->
 *     EOF) center center no-repeat;
 *  }
 */

var fs = require(‘fs‘);
var path = require(‘path‘);
var crypto = require(‘crypto‘);
var child_proc = require(‘child_process‘);
var jarPath = path.join(__dirname, ‘../jar/svg-to-png.jar‘);

module.exports = function(myless, savePath){
    ‘use strict‘;

    var util = myless.util, fileProps, error;
    var args = [].slice.call(arguments, 1).map(function(i){ return i.value; });
    var data = util.svg.parseInput.apply(null, args.slice(1));
    var picPath = util.file.getRefFilePath(savePath, false);
    var svgCode = util.svg.getSVGCode(data.attrs, data.cont);
    var contMd5 = crypto.createHash(‘md5‘).update(svgCode).digest(‘hex‘);

    // 若png文件已经存在,检查生成图片的svg内容是否发生变化,若没有变化不再重新生成图片.
    if(fs.existsSync(picPath)){
        fileProps = util.file.getFileProps(picPath);
        if(fileProps["cont-md5"] == contMd5) {
            util.console.log(‘svg-to-png: <green>svg conttent not change, return cached file!</green>‘);
            return picPath;
        }
    }

    var codeBase64 = new Buffer(svgCode).toString(‘base64‘);
    var pathBase64 = new Buffer(picPath).toString(‘base64‘);
    var command = "java -jar " + jarPath + ‘ ‘ + codeBase64 + ‘ ‘ + pathBase64;

    try {
        util.file.mkFilePath(picPath);
        child_proc.execSync(command, { encoding: ‘utf8‘ });

    } catch(e) {
        error = e;

    }finally{
        if(error){
            if( fs.existsSync(picPath) ) {
                fs.unlinkSync(picPath);
            }        

            throw (‘‘
                + ‘svg-to-png.js: create png file : "‘ + savePath.value + ‘" error!\n‘
                + ‘svg  content:‘ + svgCode
                + ‘error info  :‘ + error
            );
        }
    }

    util.file.setFileProps(picPath, { "cont-md5" : contMd5 });
    return picPath;
}

项目中的实际应用

这个功能在我最近的正在开发这个这个项目中用来完成对IE78的兼容性处理。在这个项目中设计师设计对页面的部分区域设计了圆角和圆形背景效果,而且这个页面有6套皮肤,在不同的皮肤设置下这些细节颜色会发生变化。在这个项目中需要做对低端浏览器做兼容处理的主要有以下图中所示的四处

其中倒计时区域的背景和边框与点赞按钮的背景和边框颜色均相同,而底部的奖品设置标题左侧的背景圆圈与底部的奖品后面的背景圆圈颜色不同。因此如果有5套不同皮肤的话,我只需要测量5组不同的颜色数据,然后根据这5组数据生成图片就好了。因此使用了如下的less代码来完成此功能

/**
 * @fileOverview  ie9以下浏览器兼容样式.
 * @since   2015.07.10
 */

.mix-circle-pic-bg(@saveTo, @color:#c7173d, @width:176) {
    @r: @width / 2;
    @bg-image : svg-to-png(@saveTo,
      ‘[email protected]{width}‘, ‘[email protected]{width}‘, <<EOF
      <circle cx="@{r}" cy="@{r}" r="@{r}" stroke="none" fill="@{color}"/>
    EOF);

    *background-image: tbcdn-uri(@bg-image);
    background-image : data-uri(@bg-image);
    background-repeat: no-repeat;
}

.mix-theme(
        @theme,
        @time-rect-color,
        @time-border-color,
        @prize-title-circle-color,
        @prize-item-circle-color) { 

    @toolbar-circle-color: @time-rect-color;
    @toolbar-shadow-color: @time-border-color;

    [email protected]{theme} {
        .main .time-card {
            background-color: transparent;
            .time-card-inner { background-color: transparent; }

            @bg-image : svg-to-png(‘./img/@{theme}-time-card-bg.png‘,
                ‘width=290px‘, ‘height=169px‘, <<EOF
                    <rect x="0" y="3" width="290" height="166" rx="6" ry="6" fill="@{time-border-color}"/>
                    <rect x="0" y="0" width="290" height="166" rx="6" ry="6" fill="@{time-rect-color}"/>
                EOF
            );

            background-image: data-uri(@bg-image);
            *background-image: tbcdn-uri(@bg-image);
        }

        .main .toolbar-card {
            @bg-image: svg-to-png(‘./img/@{theme}-toolbar-bg.png‘,
                ‘width=100px‘, ‘height=104px‘, <<EOF
                    <circle cx="50" cy="54" r="50" fill="@{toolbar-shadow-color}"/>
                    <circle cx="50" cy="50" r="50" fill="@{toolbar-circle-color}"/>
                EOF
            ); 

            &:before { display: none; }
            background: data-uri(@bg-image) 0 0 no-repeat;
            *background: tbcdn-uri(@bg-image) 0 0 no-repeat;
        }

        .footer .prize-arrow-wrap {
            &:before { display: none !important; }
            .mix-circle-pic-bg(‘./img/@{theme}-prize-title-bg.png‘,@prize-title-circle-color, 28);
        }
        .footer .prize-card {
            &:before { display: none !important; }
            .mix-circle-pic-bg(‘./img/@{theme}-prize-item-bg.png‘, @prize-item-circle-color, 176);
        }
    }
} 

/*-= 主题部分 =---------------*/
// 主题名称前面加t-是因为这个几个颜色是less放到关键字less会自动转换为对应颜色的16进制值
// 我被这个bug坑了很久,调试了半天才发现。
.mix-theme(t-pink, #c21339, #de2e54, #e4214b, #c7173d);
.mix-theme(t-blue, #2581d5, #3da2ff, #2581d5, #2273bd);
.mix-theme(t-green, #7ea41a, #acd635, #96bc24, #719103);
.mix-theme(t-violet, #9978d5, #bc98ff, #9c7bd9, #7758ad);
.mix-theme(t-red, #cf1e29, #fa3f43, #d6202d, #bf222c);

总的来说运行后的效果还算不错,图片的质量与photoshop的略差一些,但也还算能用,面面是

用代码生成的不同颜色的点赞按钮背景:

一些注意事项

  • batik支持半透明但不支持css3的rgba函数,需要使用fill-opacity属性来指定透明度。例如你想给一个rect制定半透明的填充色,你不能这样设置:"fill=‘rgba(0,0,0, 0.5)‘",需要这样:"fill=‘#000‘ fill-opacity=‘0.5‘"
  • pink, red 这类css中的颜色常量在less中会被替换为对应的16进制标示,命名变量时要避免使用

thanks

  • 感想@张霸、@思永在java和eclipse操作方面给予的技术支持

参考资料

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-10 03:31:56

自动图片生成在前端开发中的一些尝试的相关文章

自己主动图片生成在前端开发中的一些尝试

图片处理在前端开发过程中占领了不少的时间.非常是累人.在本文中我们不讨论怎样提高切图的效率.我们讨论还有一个问题:怎样处理设计稿中的一些简单图形. 不知道你又没有遇到过这样的烦恼:"设计师给你的精致的PSD中有一个简单图形,就是那用用多边形.圆形和线条组成的图形.这个图形用css3实现不了,或者能实现.但为了兼容某些浏览器不能用css3来实现,仅仅能切图.好的.你非常快切完了并在样式中引用了. 但没过多久需求上要求改下图片的颜色,而这是你已经将导出图片的那个PSD删除了,你还得又一次在设计稿中把

WEB前端开发中的图片压缩

web前端开发中,图片的重要性不言而喻,而由于一些图片的大小加上现在国内的网速不给力等种种原因,我们非常有必要对网站使用的图片进行压缩,压缩图片必然会带来图片质量的损失,我们要尽可能的在质量降低很小的情况下压缩图片,以便让网站更快的加载,提高用户体验度. 我在工作中,压缩图片用到了三个方法,分享给大家: 一.使用windows自带的画图工具 1.使用画图工具打开想要压缩的图片:2.什么都不用做,直接另存为你想要的图片格式,你会发现它比源文件小了很多,而且质量看不出来损失. 这个方法简单,快捷,压

一探前端开发中的JS调试技巧

前言:调试技巧,在任何一项技术研发中都可谓是必不可少的技能.掌握各种调试技巧,必定能在工作中起到事半功倍的效果.譬如,快速定位问题.降低故障概率.帮助分析逻辑错误等等.而在互联网前端开发越来越重要的今天,如何在前端开发中降低开发成本,提升工作效率,掌握前端开发调试技巧尤为重要. 本文将一一讲解各种前端JS调试技巧,也许你已经熟练掌握,那让我们一起来温习,也许有你没见过的方法,不妨一起来学习,也许你尚不知如何调试,赶紧趁此机会填补空白. 骨灰级调试大师Alert 那还是互联网刚刚起步的时代,网页前

关于前端开发中的“收口”思想

什么是收口 所谓条条大路通罗马,但如果让我来设计通向罗马的各种大路,我至少会做两件事情: ① 让罗马只有一个入口 ② 让罗马只有一个出口 这样做的好处是,无论你路从哪来,我可以统一在入口处给你打上各种标志,我也可以在你离开罗马时给你留点纪念.当然罗马自然不只一个出口入口,但是每个出口入口一定有一套相同的规定,否则就会出问题. 具体到当今的工作场景,高速公路又是一个收口的好例子,进入高速公路时候得经过收费站做点标志,离开时候也会做点操作,如果没有这种收口,不论是缴费工作,流量统计或者其他都是无法统

前端开发中的JS调试技巧

前言:调试技巧,在任何一项技术研发中都可谓是必不可少的技能.掌握各种调试技巧,必定能在工作中起到事半功倍的效果.譬如,快速定位问题.降低故障概率.帮助分析逻辑错误等等.而在互联网前端开发越来越重要的今天,如何在前端开发中降低开发成本,提升工作效率,掌握前端开发调试技巧尤为重要. 本文将一一讲解各种前端JS调试技巧,也许你已经熟练掌握,那让我们一起来温习,也许有你没见过的方法,不妨一起来学习,也许你尚不知如何调试,赶紧趁此机会填补空白. 骨灰级调试大师Alert 那还是互联网刚刚起步的时代,网页前

【名词】在前端开发中的“轮子”

最近在随便刷一些论坛,文档,常见一个名词"轮子". 自我理解:已封装完成的库.换个名词,组件.例如:图片轮播组建:swiper等,菜单栏组件. 在前端开发中,我们需要在满足条件下对于平台进行兼容,众所周知的IE系列,那么如果有一份已经写好的能够兼容各个浏览器的代码,进行封装以后直接引入使用显然会事半功倍. 作为练习,对于市场上主流的"轮子"进行仿造可以增加经验,也可以自己造轮子,只有自己动手做了才能够知道其中的知识点.

【转载】一探前端开发中的JS调试技巧

友情提示:文中涉及较多Gif演示动画,移动端请尽量在Wifi环境中阅读 前言:调试技巧,在任何一项技术研发中都可谓是必不可少的技能.掌握各种调试技巧,必定能在工作中起到事半功倍的效果.譬如,快速定位问题.降低故障概率.帮助分析逻辑错误等等.而在互联网前端开发越来越重要的今天,如何在前端开发中降低开发成本,提升工作效率,掌握前端开发调试技巧尤为重要. 本文将一一讲解各种前端JS调试技巧,也许你已经熟练掌握,那让我们一起来温习,也许有你没见过的方法,不妨一起来学习,也许你尚不知如何调试,赶紧趁此机会

前端开发中务必要转义的三处场景

出于这样或那样的原因,我们在传输.存储.展现字符串时需要进行转义操作,防止不可控的事情发生.下面我将分三处场景描述,有的里边确实有坑,希望大家看完后都有所收获.欢迎积极留言补充. 场景1:使用URL 前端开发中,我们经常会使用到URL,比如博客查询的表单action:"http://eastme.me?q=前端".Ajax发送Get\Post请求.跳转至网址:"http://www.eastme.me/个人简介"等等.这些请求的URL经常会出现汉字,尤其是当表单提交

总结前端开发中的一些特殊规范

前端日子工作太忙没时间发随笔,现在来总结一些前端开发中的特殊规范(常规的规范就不赘述了),希望能让各位收益,也欢迎提出异议. 一. 文件系统 一个有条理的文件系统可以为后期的维护提供便利,起码寻找某个页面的某张图片时不用对着url地址顺藤摸瓜找半天,如果能做到不看url也能准确猜中某页面文件的所在地,那这个文件系统便是合格的. 先来看一个不合格的文件存放方式: 如上图,该目录下共有2个css文件夹.2个js文件夹以及3个存放图片的文件夹(“dyp2p”文件夹里也是放置图片的),同时还有许多人经常