1.SVG格式
SVG是一种图形文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形。它是基于XML(Extensible Markup Language),由World Wide Web Consortium(W3C)联盟进行开发的。严格来说应该是一种开放标准的矢量图形语言,可让你设计激动人心的、高分辨率的Web图形页面。用户可以直接用代码来描绘图像,可以用任何文字处理工具打开SVG图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来观看。
来源:百度百科-SVG格式
2.前端画流程图替换svg格式
2.1.替换接口/modeler/editor/stencilset中的view
为设计的新的svg图片即可,但是前端渲染的时候有一个问题,前端的框架默认svg左边开始点不是从0像素开始,顶部也不是从0像素开始,需要使用设计的原始svg进行一定的偏移和前端画流程图框架的偏移量对其,这样图片才正好在正中间
2.2.如下图,保存的svg需要有一定的偏移
2.3.替换后的接口/modeler/editor/stencilset获取的stencilsetjson数据
替换如下的view为井经过修改偏移后的svg即可,其中svg可以直接使用浏览器打开,在response中赋值复制出svg格式文件,可以使用xml格式压缩工具压缩后再替换view
3.后端渲染实时流程图替换svg格式
通过流程instanceId获取实时流程图,后端是实时画的图,不是从stencilsetjson.json中获取view画出来的,所以需要自己定制画网关的逻辑,大概思路为:
从stencilsetjson.json获取orgView的原始svg图片>>转换为BufferedImage>>重写drawExclusiveGateway写网关逻辑>>>使用g.drawImage和orgView画替换的网关
3.1 其中需要说明,stencilsetjson.json的view和orgView的区别
看下图,orgView为左边从0像素开始,顶点从0像素开始
3.2 svg格式转换为BufferedImage
使用PNGTranscoder转换为BufferedImage
引入依赖
<!-- 解析SVG引入 -->
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.7</version>
<scope>compile</scope>
</dependency>
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.batik.transcoder.image.ImageTranscoder;
import javax.imageio.ImageIO;
public class OffScreenSVGRenderer {
/**
* 将svgCode转换成png文件,直接输出到流中
*
* @param svgCode
* svg代码
* @param outputStream
* 输出流
* @throws TranscoderException
* 异常
* @throws IOException
* io异常
*/
public static void convertToPng(String svgCode, float width, float height, OutputStream outputStream) throws TranscoderException, IOException {
try {
byte[] bytes = svgCode.getBytes("utf-8");
PNGTranscoder t = new PNGTranscoder();
TranscoderInput input = new TranscoderInput(new ByteArrayInputStream(bytes));
TranscoderOutput output = new TranscoderOutput(outputStream);
// 增加图片的属性设置(单位是像素)
t.addTranscodingHint(ImageTranscoder.KEY_WIDTH, new Float(width));
t.addTranscodingHint(ImageTranscoder.KEY_HEIGHT, new Float(height));
t.transcode(input, output);
outputStream.flush();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 将svg格式图片输出为BufferedImage流
* @param svgContent
* @param width
* @param height
* @return
*/
public static BufferedImage renderToImage(String svgContent, float width, float height) {
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
OffScreenSVGRenderer.convertToPng(svgContent, width, height, output);
byte[] data = output.toByteArray();
ByteArrayInputStream input = new ByteArrayInputStream(data);
BufferedImage image = ImageIO.read(input);
return image;
} catch (TranscoderException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
参考:SVG2PNG(前台和后台将SVG转换为PNG)–amcharts导出png
3.3 之前使用ImageRenderer读取SVG转换为BufferedImage报错
java.io.IOException: SAX2 driver class org.apache.xerces.parsers.SAXParser not found
在pom.xml文件中加上xercesImpl的以下相关依赖并没有解决该报错,贴上方法renderToImage的代码,各位可以参考
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.11.0</version>
</dependency>
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
import org.apache.batik.gvt.renderer.ImageRenderer;
import org.apache.batik.gvt.renderer.ImageRendererFactory;
import org.w3c.dom.svg.SVGDocument;
public class OffScreenSVGRenderer {
public static BufferedImage renderToImage(SVGDocument document, int width, int height, boolean stretch){
ImageRendererFactory rendererFactory;
rendererFactory = new ConcreteImageRendererFactory();
ImageRenderer renderer = rendererFactory.createStaticImageRenderer();
GVTBuilder builder = new GVTBuilder();
BridgeContext ctx = new BridgeContext(new UserAgentAdapter());
ctx.setDynamicState(BridgeContext.STATIC);
GraphicsNode rootNode = builder.build(ctx, document);
renderer.setTree(rootNode);
float docWidth = (float) ctx.getDocumentSize().getWidth();
float docHeight = (float) ctx.getDocumentSize().getHeight();
float xscale = width/docWidth;
float yscale = height/docHeight;
if(!stretch){
float scale = Math.min(xscale, yscale);
xscale = scale;
yscale = scale;
}
AffineTransform px = AffineTransform.getScaleInstance(xscale, yscale);
double tx = -0 + (width/xscale - docWidth)/2;
double ty = -0 + (height/yscale - docHeight)/2;
px.translate(tx, ty);
//cgn.setViewingTransform(px);
renderer.updateOffScreen(width, height);
renderer.setTree(rootNode);
renderer.setTransform(px);
//renderer.clearOffScreen();
renderer.repaint(new Rectangle(0, 0, width, height));
return renderer.getOffScreen();
}
}
3.4 重写drawExclusiveGateway写网关逻辑
后端画流程图调用堆栈
其中CustomProcessDiagramGenerator和CustomProcessDiagramCanvas为自定义的重写类
其中public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator
其中public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas
java.lang.NumberFormatException: For input string: "5F"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.valueOf(Integer.java:766)
at com.demo.its.activiti.config.CustomProcessDiagramCanvas.drawExclusiveGateway(CustomProcessDiagramCanvas.java:212)
at org.activiti.image.impl.DefaultProcessDiagramGenerator$13.draw(DefaultProcessDiagramGenerator.java:255)
at com.demo.its.activiti.config.CustomProcessDiagramGenerator.drawActivity(CustomProcessDiagramGenerator.java:91)
at com.demo.its.activiti.config.CustomProcessDiagramGenerator.generateProcessDiagram(CustomProcessDiagramGenerator.java:62)
at com.demo.its.activiti.config.CustomProcessDiagramGenerator.generateDiagram(CustomProcessDiagramGenerator.java:340)
at com.demo.its.activiti.controller.ProcessController.getActivitiProccessImage(ProcessController.java:503)
at com.demo.its.activiti.controller.ProcessController.readResource(ProcessController.java:440)
at com.demo.its.activiti.controller.ProcessController$$FastClassBySpringCGLIB$$d3ae0527.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:687)
3.5 DefaultProcessDiagramGenerator为流程图生成器策略选择器
看源代码可以发现DefaultProcessDiagramGenerator为流程图生成器策略选择器,该方法定义了Activity根据何种具体实体类调用DefaultProcessDiagramCanvas的何种画的方法
3.5 DefaultProcessDiagramCanvas为具体的画流程图组件实现类
DefaultProcessDiagramCanvas的默认的drawExclusiveGateway方法虽然不是接口形式,但是不影响继承类CustomProcessDiagramCanvas重写该方法
3.6 在自定义的CustomProcessDiagramCanvas 方法中重写的drawExclusiveGateway方法
public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
// 缓存自定义的svg图片
private static Map<String, BufferedImage> bufferedImageMap = new HashMap<>();
public void drawExclusiveGateway(GraphicInfo graphicInfo, double scaleFactor) {
Polygon rhombus = new Polygon();
int x = (int)graphicInfo.getX();
int y = (int)graphicInfo.getY();
int width = (int)graphicInfo.getWidth();
int height = (int)graphicInfo.getHeight();
rhombus.addPoint(x, y + height / 2);
rhombus.addPoint(x + width / 2, y + height);
rhombus.addPoint(x + width, y + height / 2);
rhombus.addPoint(x + width / 2, y);
String id = "ExclusiveGateway";
BufferedImage image = null;
if (bufferedImageMap.containsKey(id)) {
image = bufferedImageMap.get(id);
System.out.printf("命中网关缓存图片.");
} else {
JSONObject stencilsetJson = StencilsetRestResource.stencilsetJson;
JSONArray stencils = stencilsetJson.getJSONArray("stencils");
Map<String, JSONObject> groupStencils = stencils.stream().collect(
Collectors.toMap(item -> ((JSONObject) item).getString("id"), item -> ((JSONObject) item))
);
String xmlContent = groupStencils.get(id).getString("orgView");
image = OffScreenSVGRenderer.renderToImage(xmlContent, width, height);
}
if (null == image) {
return;
}
bufferedImageMap.put(id, image);
int imageX = (int)(graphicInfo.getX() + graphicInfo.getWidth() / 2.0D - (double)(image.getWidth() / 2) * scaleFactor);
int imageY = (int)(graphicInfo.getY() + graphicInfo.getHeight() / 2.0D - (double)(image.getHeight() / 2) * scaleFactor);
this.g.drawImage(image, imageX, imageY, (int)((double)image.getWidth() / scaleFactor), (int)((double)image.getHeight() / scaleFactor), (ImageObserver)null);
}
}