使用SVG简单处理图片
最近3周,做了几个照片打印的模版,将前端的工作进一步延伸到用户可以“把玩”的距离。 如标题中提到的简单一样,这几个模板都不复杂;二是用SVG处理照片打印模板更简单,同时SVG作为矢量文件可以高质量的转换到PDF。
虽然SVG也是符合xml规范的标签,但之前还是差不多10年前接触过,已经基本上忘干净了。便找到了文末参考中的一些资料,花了一天时间,将SVG详细查看了一遍——但现在又基本上忘干净了——赶紧参考代码和OneNode中的记录把用到的这部分细节记录如下,将两个简单、典型的案例分析如下。
主要阅读的资料是[SVG应用指南]和[SVG入门教程],最意外的收获是无意中找到了周爱民老师的博客Aimingoo,在最后关键的将SVG中转化为PDF的操作中使用的也是周爱民老师博客中提到的Cairo,周爱民老师博客的详细和严谨真的很有帮助。
SVG语法简介
SVG跟HTML类似的嵌套标签,既有rect
,circle
、line
等用来标记描述图形的标签,也有text
、tspan
等用来标记文本的标签。
SVG支持CSS,所以图片处理中的裁切就等价于了超出隐藏。SVG元素上内联的CSS属性与HTML元素上的CSS属性有所不同,但总体原则保持不变;也支持外联的CSS样式表,可以如下方式引入:
<?xml-stylesheet type="text/css" href="svg-stylesheet.css" ?>
以竖版照片模板为例,一个完整的SVG文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
xml:space="preserve"
viewBox="0 0 117 153"
preserveAspectRatio="xMinYMin slice"
x="0"
y="0"
style="enable-background:new 0 0 117 153;"
>
<rect x="0" y="0" width="117" height="153" style="fill:#FFFFFF;" />
<svg
viewBox="0 0 585 765"
preserveAspectRatio="xMinYMin slice"
width="114"
height="150"
x="1.5"
y="1.5"
>
<image
xlink:href="http://pic09.babytreeimg.com/2017/0914/FnglV7T730tHagl3BkHReAoNcPcD"
width="960"
height="1280"
transform="translate(-72, -272)"
style="overflow: hidden;"
/>
</svg>
</svg>
由于打印厂商要求每笔订单要输出为同一份PDF文件,所以横版照片也需要旋转成竖版,以方便PDF的生成。对应的竖版照片模板如下所示:
<?xml version="1.0" encoding="utf-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
xml:space="preserve"
viewBox="0 0 117 153"
preserveAspectRatio="xMinYMin slice"
x="0"
y="0"
style="enable-background:new 0 0 117 153;"
>
<rect x="0" y="0" width="117" height="153" style="fill:#FFFFFF;" />
<g
transform="translate(115.5, 1.5) rotate(90 0, 0)"
>
<svg
viewBox="0 0 150 114"
preserveAspectRatio="xMinYMin slice"
width="150"
height="114"
style="enable-background:new 0 0 150 114;"
>
<svg
viewBox="0 0 993 662"
preserveAspectRatio="xMinYMin slice"
width="150"
height="114"
x="0"
y="0"
>
<image
xlink:href="http://pic09.babytreeimg.com/2017/1017/FhdHO0o0-lA1tEfuZnIQF04MmNAy_webp_b.webp"
width="1280"
height="960"
transform="translate(-75, -298)"
style="overflow: hidden;"
/>
</svg>
</svg>
</g>
</svg>
照片模板中使用到的标签和关键属性用法简介
svg
标签
viewBox="0 0 117 153"
,定义当前svg内部的元素定位所使用的坐标系,4个值分别对应最小x坐标,最小y坐标,宽度和高度。preserveAspectRatio="xMinYMin slice"
,定义内部元素的绘制起点和超出显示范围的处理策略。在当前的照片模板中直接裁掉,即超出隐藏即可。width="114"
和height="150"
,定义当前元素的宽度,尺寸参考最近祖先元素的viewBox属性定义。x="1.5"
和y="1.5"
,SVG内的所有元素都可以看作绝对定位,相对于最近祖先元素中viewBox属性定义。
svg
标签可以嵌套使用,可以使用viewBox的定义和嵌套使用,使用相对的方式便捷处理。
rect
标签
绘制长方形,详细参考文档。
image
标签
绘制图片,详细参考文档。
g
标签
将元素分组,其后可以直接使用transform="translate(115.5, 1.5) rotate(90 0, 0)"
将其定位。
最终模板
竖版照片模板如下:
<?xml version="1.0" encoding="utf-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
xml:space="preserve"
viewBox="0 0 117 153"
preserveAspectRatio="xMinYMin slice"
width="117"
height="153"
x="0"
y="0"
style="enable-background:new 0 0 117 153;"
>
<rect x="0" y="0" width="117" height="153" style="fill:#FFFFFF;" />
<svg
viewBox="0 0 {{ $imageViewBoxWidth }} {{ $imageViewBoxHeight }}"
preserveAspectRatio="xMinYMin slice"
width="114"
height="150"
x="1.5"
y="1.5"
>
<image
xlink:href="{{ $photo_url }}"
width="{{ $imageWidth }}"
height="{{ $imageHeight }}"
transform="translate(-{{ $offsetLeft }}, -{{ $offsetTop }})"
style="overflow: hidden;"
/>
</svg>
</svg>
横版照片模板如下:
<?xml version="1.0" encoding="utf-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
xml:space="preserve"
viewBox="0 0 117 153"
preserveAspectRatio="xMinYMin slice"
x="0"
y="0"
style="enable-background:new 0 0 117 153;"
>
<rect x="0" y="0" width="117" height="153" style="fill:#FFFFFF;" />
<g
transform="translate(115.5, 1.5) rotate(90 0, 0)"
>
<svg
viewBox="0 0 150 114"
preserveAspectRatio="xMinYMin slice"
width="150"
height="114"
style="enable-background:new 0 0 150 114;"
>
<svg
viewBox="0 0 {{ $imageViewBoxWidth }} {{ $imageViewBoxHeight }}"
preserveAspectRatio="xMinYMin slice"
width="150"
height="114"
x="0"
y="0"
>
<image
xlink:href="{{ $photo_url }}"
width="{{ $imageWidth }}"
height="{{ $imageHeight }}"
transform="translate(-{{ $offsetLeft }}, -{{ $offsetTop }})"
style="overflow: hidden;"
/>
</svg>
</svg>
</g>
</svg>
原图缩放根据SVG viewBox特性处理,参数意义如下。
$offsetLeft
,代表图片水平偏移$offsetTop
,代表图片垂直偏移$imageViewBoxWidth
,代表图片裁切宽度$imageViewBoxHeight
,代表图片裁切高度$imageWidth
,代表图片尺寸宽度$imageHeight
,代表图片尺寸高度$photo_url
,代表原图,非上面6个值计算使用的压缩图。由于原图和压缩图是等比例的,5和6两个值来相对表示原图的尺寸,而1、2、3、4都相对于5和6来度量,计算的参考系保持一致。
生成PDF
SVG也是一种图片,图片的转换最常使用的工具是imagemagick,使用下面的代码即可将SVG转化为对应的300DPI的JPG图片,但这本质上是将矢量的SVG转化了栅格化的JPG(即使生成PDF,也是JPG再转为PDF)。而且转换的图片质量跟AI的导出相比过于低,尤其转换台历之类的矢量文字时,效果总是期待更好。
而且发现imagemagick对SVG的支持有限,不支持超出隐藏之类的CSS语法。
magick -density 300 -quality 100 -colorspace RGB -compress none january.svg january.jpg
通过cloudconvert转化的svg却是矢量效果,猜测PDF可以封装支持SVG。用Chrome打印出PDF发现确实是矢量输出,便用Chrome的Headless模式将其打印出来:
chrome --headless --disable-gpu --print-to-pdf file:///Users/******/Desktop/svg/january.svg
但Chrome没有在服务器上安装成功,便继续搜索查找解决办法,这时便打开了周爱民老师的博客,虽然标题是在电子书中使用SVG,抱着崇拜的心态仔细读了一遍,却得到了imagemagick出现上述的原因的原因,也获得了Cairo的提示。
另一个角度,晚上跟一个老大哥同事聊天得知打印使用的是PostScript,沿着这个思路搜索了一下到了一些解决方案。
最终是使用Cairo的Python封装完美实现的。