1. 程式人生 > >activiti繪製流程圖,線上顯示文字,審批過的節點新增審批人的名字

activiti繪製流程圖,線上顯示文字,審批過的節點新增審批人的名字

最有時間優化了一下activiti的繪製圖片工具類,主要用於在領導審批的時候展示的漂亮,直接上程式碼吧,

/** 放在我們的業務程式碼中,穿個流程例項id進來,返回一個字元陣列,然後該怎麼處理你們應該知道怎麼做了吧
*/
public byte[] getImage(String processInstanceId) throws ServiceRuntimeException
{
    try
    {
        //  獲取歷史流程例項
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();
        if (historicProcessInstance != null)
        {
            // 獲取流程定義
            ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                    .getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId());
            List<HistoricActivityInstance> historicActivityInstanceList = historyService
                    .createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().asc().list();

            // 已執行的節點ID集合
            List<String> executedActivityIdList = new ArrayList<String>();
            int index = 1;
            for (HistoricActivityInstance activityInstance : historicActivityInstanceList)
            {
                executedActivityIdList.add(activityInstance.getActivityId());
                index++;
            }
            // 獲取流程圖影象字元流
            InputStream imageStream = ProcessDiagramGenerator.generateDiagram(processDefinition, "png", executedActivityIdList,processInstanceId);

            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = -1;
            while((len = imageStream.read(buffer)) != -1){
                outStream.write(buffer, 0, len);
            }
            outStream.close();
            imageStream.close();
            return outStream.toByteArray();

        }
        return null;
    }
    catch (Exception e)
    {
        throw new RuntimeException("獲取流程圖失敗!" + e.getMessage());
    }
}
剩下的就是兩個繪製流程圖的工具類,直接複製就可以了
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package *

import com.cfne.cuckoo.oa.domain.entity.category.FlowCategoryEntity;
import com.cfne.cuckoo.oa.service.category.FlowCategoryService;
import com.cfne.cuckoo.oa.service.flow.history.ActHiProcinstExtService;
import com.cfne.cuckoo.oa.service.flow.history.ActHiTaskinstService;
import com.cfne.cuckoo.oa.service.flow.running.ActRuTaskService;
import com.cfne.cuckoo.oa.utils.SpringUtil;
import jdk.nashorn.internal.runtime.ECMAException;
import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.impl.bpmn.parser.BpmnParse;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.*;
import org.activiti.engine.impl.pvm.process.Lane;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.annotation.*;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.*;

/**
 * 繪製流程工具類
 * 
 *
 */
public class ProcessDiagramGenerator  {
    static final Logger logger = Logger.getLogger(ProcessDiagramGenerator.class);
    protected static final Map<String, ActivityDrawInstruction> activityDrawInstructions = new HashMap<String, ActivityDrawInstruction>();

    static {
        // start event
        activityDrawInstructions.put("startEvent", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawNoneStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // start timer event
        activityDrawInstructions.put("startTimerEvent", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawTimerStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // signal catch
        activityDrawInstructions.put("intermediateSignalCatch", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawCatchingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // signal throw
        activityDrawInstructions.put("intermediateSignalThrow", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawThrowingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // end event
        activityDrawInstructions.put("endEvent", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawNoneEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // error end event
        activityDrawInstructions.put("errorEndEvent", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawErrorEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // error start event
        activityDrawInstructions.put("errorStartEvent", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawErrorStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // task
        activityDrawInstructions.put("task", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // user task
        activityDrawInstructions.put("userTask", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawUserTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // script task
        activityDrawInstructions.put("scriptTask", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawScriptTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // service task
        activityDrawInstructions.put("serviceTask", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawServiceTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // receive task
        activityDrawInstructions.put("receiveTask", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawReceiveTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // send task
        activityDrawInstructions.put("sendTask", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawSendTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // manual task
        activityDrawInstructions.put("manualTask", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawManualTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // businessRuleTask task
        activityDrawInstructions.put("businessRuleTask", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawBusinessRuleTask((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // exclusive gateway
        activityDrawInstructions.put("exclusiveGateway", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawExclusiveGateway(activityImpl, activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // inclusive gateway
        activityDrawInstructions.put("inclusiveGateway", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawInclusiveGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // parallel gateway
        activityDrawInstructions.put("parallelGateway", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawParallelGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

        // Boundary timer
        activityDrawInstructions.put("boundaryTimer", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawCatchingTimerEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // Boundary catch error
        activityDrawInstructions.put("boundaryError", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawCatchingErroEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // Boundary signal event
        activityDrawInstructions.put("boundarySignal", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawCatchingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // timer catch event
        activityDrawInstructions.put("intermediateTimer", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawCatchingTimerEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(),
                        activityImpl.getHeight());
            }
        });

        // subprocess
        activityDrawInstructions.put("subProcess", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                Boolean isExpanded = (Boolean) activityImpl.getProperty(BpmnParse.PROPERTYNAME_ISEXPANDED);
                Boolean isTriggeredByEvent = (Boolean) activityImpl.getProperty("triggeredByEvent");
                if (isTriggeredByEvent == null) {
                    isTriggeredByEvent = Boolean.TRUE;
                }
                if (isExpanded != null && isExpanded == false) {
                    processDiagramCreator.drawCollapsedSubProcess((String) activityImpl.getProperty("name"), activityImpl.getX(),
                            activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
                } else {
                    processDiagramCreator.drawExpandedSubProcess((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                            activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
                }
            }
        });

        // call activity
        activityDrawInstructions.put("callActivity", new ActivityDrawInstruction() {
            @Override
            public void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl) {
                processDiagramCreator.drawCollapsedCallActivity((String) activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
                        activityImpl.getWidth(), activityImpl.getHeight());
            }
        });

    }

    /**
     * Generates a PNG diagram image of the given process definition, using the
     * diagram interchange information of the process.
     */
    public static InputStream generatePngDiagram(ProcessDefinitionEntity processDefinition,String processInstanceId) throws Exception {
        return generateDiagram(processDefinition, "png", Collections.<String>emptyList(),processInstanceId);
    }

    /**
     * Generates a JPG diagram image of the given process definition, using the
     * diagram interchange information of the process.
     */
    public static InputStream generateJpgDiagram(ProcessDefinitionEntity processDefinition,String processInstanceId) throws  Exception{
        return generateDiagram(processDefinition, "jpg", Collections.<String>emptyList(),processInstanceId);
    }

    protected static ProcessDiagramCanvas initProcessDiagramCanvas(ProcessDefinitionEntity processDefinition) {
        int minX = Integer.MAX_VALUE;
        int maxX = 0;
        int minY = Integer.MAX_VALUE;
        int maxY = 0;

        if (processDefinition.getParticipantProcess() != null) {
            ParticipantProcess pProc = processDefinition.getParticipantProcess();

            minX = pProc.getX();
            maxX = pProc.getX() + pProc.getWidth();
            minY = pProc.getY();
            maxY = pProc.getY() + pProc.getHeight();
        }

        for (ActivityImpl activity : processDefinition.getActivities()) {

            // width
            if (activity.getX() + activity.getWidth() > maxX) {
                maxX = activity.getX() + activity.getWidth();
            }
            if (activity.getX() < minX) {
                minX = activity.getX();
            }
            // height
            if (activity.getY() + activity.getHeight() > maxY) {
                maxY = activity.getY() + activity.getHeight();
            }
            if (activity.getY() < minY) {
                minY = activity.getY();
            }

            for (PvmTransition sequenceFlow : activity.getOutgoingTransitions()) {
                List<Integer> waypoints = ((TransitionImpl) sequenceFlow).getWaypoints();
                for (int i = 0; i < waypoints.size(); i += 2) {
                    // width
                    if (waypoints.get(i) > maxX) {
                        maxX = waypoints.get(i);
                    }
                    if (waypoints.get(i) < minX) {
                        minX = waypoints.get(i);
                    }
                    // height
                    if (waypoints.get(i + 1) > maxY) {
                        maxY = waypoints.get(i + 1);
                    }
                    if (waypoints.get(i + 1) < minY) {
                        minY = waypoints.get(i + 1);
                    }
                }
            }
        }

       if (processDefinition.getLaneSets() != null && processDefinition.getLaneSets().size() > 0) {
            for (LaneSet laneSet : processDefinition.getLaneSets()) {
                if (laneSet.getLanes() != null && laneSet.getLanes().size() > 0) {
                    for (Lane lane : laneSet.getLanes()) {
                        // width
                        if (lane.getX() + lane.getWidth() > maxX) {
                            maxX = lane.getX() + lane.getWidth();
                        }
                        if (lane.getX() < minX) {
                            minX = lane.getX();
                        }
                        // height
                        if (lane.getY() + lane.getHeight() > maxY) {
                            maxY = lane.getY() + lane.getHeight();
                        }
                        if (lane.getY() < minY) {
                            minY = lane.getY();
                        }
                    }
                }
            }
        }
        return new ProcessDiagramCanvas(maxX + 10, maxY + 10, minX, minY);
    }


    protected interface ActivityDrawInstruction {
        void draw(ProcessDiagramCanvas processDiagramCreator, ActivityImpl activityImpl);
    }

    /**
     * [生成流程圖,返回輸入流]
     *
     * @param processDefinition
     * @param imageType
     * @param highLightedActivities
     * @return
     * @Author: [Double]
     * @CreateDate: [2015-10-22]
     * @Version: [v2.0.0]
     */
    public static InputStream generateDiagram(ProcessDefinitionEntity processDefinition, String imageType, List<String> highLightedActivities,String processInstanceId) throws Exception {
        // 通過流程圖畫布繪製出對應影象型別的流程圖圖片
        return generateDiagram(processDefinition, highLightedActivities,processInstanceId).generateImage(imageType);
    }

    /**
     * [生成流程圖,返回流程圖畫布]
     *
     * @param processDefinition
     * @param highLightedActivities
     * @return
     * @Author: [Double]
     * @CreateDate: [2015-10-22]
     * @Version: [v2.0.0]
     */
    protected static ProcessDiagramCanvas generateDiagram(ProcessDefinitionEntity processDefinition, List<String> highLightedActivities,String processInstanceId) throws Exception {
        ProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(processDefinition);

        // Draw pool shape, if process is participant in collaboration
        if (processDefinition.getParticipantProcess() != null) {
            ParticipantProcess pProc = processDefinition.getParticipantProcess();
            processDiagramCanvas.drawPoolOrLane(pProc.getName(), pProc.getX(), pProc.getY(), pProc.getWidth(), pProc.getHeight());
        }
        // Draw lanes
        if (processDefinition.getLaneSets() != null && processDefinition.getLaneSets().size() > 0) {
            for (LaneSet laneSet : processDefinition.getLaneSets()) {
                if (laneSet.getLanes() != null && laneSet.getLanes().size() > 0) {
                    for (Lane lane : laneSet.getLanes()) {
                        processDiagramCanvas.drawPoolOrLane(lane.getName(), lane.getX(), lane.getY(), lane.getWidth(), lane.getHeight());
                    }
                }
            }
        }

        // Draw activities and their sequence-flows
        // 迴圈當前流程定義中的所有節點,繪製節點和節點上的流程線,如果節點已經執行過這高亮顯示
       /* for (ActivityImpl activity : processDefinition.getActivities()) {
            drawActivity(processDiagramCanvas, activity, highLightedActivities);
        }*/
        String id2=drawActivitys(processDiagramCanvas, processDefinition, highLightedActivities,processInstanceId);

        // 繪製當前所有已執行的節點的所有流出流程線,如果流程線已被執行過,則高亮顯示
        for (int index = 1; index <= highLightedActivities.size(); index++) {
            for (ActivityImpl activity : processDefinition.getActivities()) {
                if (highLightedActivities.get(index - 1).equals(activity.getId())) {
                    drawActivityFlowHighLight(processDiagramCanvas, activity, highLightedActivities, index,id2);
                  /*  // 最後一個節點紅色顯示
                    if (index == highLightedActivities.size()) {
                        drawCurrentActivityRed(processDiagramCanvas, activity);
                    }*/
                }
            }
        }

        return processDiagramCanvas;
    }


    /**
     * [繪製流程圖中高亮顯示的流程線]
     *
     * @param processDiagramCanvas
     * @param activity
     * @param highLightedActivities
     * @Author: [Double]
     * @CreateDate: [2015-10-22]
     * @Version: [v2.0.0]
     */
    protected static void drawActivityFlowHighLight(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity, List<String> highLightedActivities, int index,String id2) throws  Exception{
        //logger.info("第【" + index + "】個節點=" + activity.getId());
        // Outgoing transitions of activity
        // 繪製當前節點的所有流出流程線,如果流程線已被執行過,則高亮顯示
        int flowIndex = 1;
        boolean isHighLightedFlow;
        boolean flag;
        boolean timeFlag;
        String id = null;
        List<Integer> waypoints = null;
        ActivityImpl lastActivityImpl = null;
        String type = (String) activity.getProperty("type");
//遍歷當前節點的所有走向
        for (PvmTransition sequenceFlow : activity.getOutgoingTransitions()) {
            //logger.info("節點的第[" + flowIndex + "]條流出流程線=" + sequenceFlow.getId());
            //變藍標識
            isHighLightedFlow = false;
            //變紅標識
            flag = false;
            timeFlag = false;
            // 當前流程線對應的後續節點
            lastActivityImpl = (ActivityImpl) sequenceFlow.getDestination();
            //logger.info("流程線[" + sequenceFlow.getId() + "]對應的後續節點ID=[" + lastActivityImpl.getId() + "]");
            // 當前節點的後續節點在需高亮顯示的節點List中,並且當前節點已經執行過就是也在高亮顯示的節點List中,
            if (highLightedActivities.contains(lastActivityImpl.getId()) && highLightedActivities.contains(activity.getId())) {
                // 獲取在已執行節點List中當前節點的下一個節點ID
                // 【注意:以下處理對於併發的處理可能不完善,遇到時再進行具體處理】
// 如果下一個節點是當前流程線指向的節點,則流程線高亮顯示
                if (index >= highLightedActivities.size()) {
                    // 沒有下一個節點,當前的流程線不高亮顯示
                    // logger.info("流程線[" + sequenceFlow.getId() + "]不需要高亮顯示");
                } else if (lastActivityImpl.getId().equals(highLightedActivities.get(index))) {
                    isHighLightedFlow = true;
                    //判斷是否包含倒數第三個節點(當前節點為起草人)
                    if(id2!=null){
                        //如果該節點是當前節點賦值變紅標識
                        if(id2.equals(activity.getId())){
                            flag=true;
                        }
                    }
                    //判斷節點型別是否為現在的節點型別是否為包含閘道器或排他閘道器
                    if (type.equals("exclusiveGateway") || type.equals("inclusiveGateway")) {
                        //h獲取流程走向中的條件
                        Object conditionText = sequenceFlow.getProperty("conditionText");
                        //如果條件不為空
                        if(conditionText!=null) {
                            //如果條件包含不同意
                            if (conditionText.toString().contains("不同意")) {
                                flag = true;
                            }
                        }
                    }

                    //  logger.info("流程線【" + sequenceFlow.getId() + "】需要高亮顯示");
                } else {
                    if (type.equals("parallelGateway")) {
                        isHighLightedFlow = true;
                    }
                    if (highLightedActivities.contains(activity.getId()) && type.equals("userTask")) {
                        isHighLightedFlow = true;
                    }
                  /* */

                }
            } else {
                if (type.equals("userTask") && !activity.getActivities().isEmpty()) {
                    //繪製定時任務的線條
                    if (activity.getActivities().get(0).getProperty("type").equals("boundaryTimer")) {
                        if (highLightedActivities.contains(activity.getActivities().get(0).getOutgoingTransitions().get(0).getDestination().getId())) {
                            isHighLightedFlow = true;
                            timeFlag = true;
                            waypoints = ((TransitionImpl) activity.getActivities().get(0).getOutgoingTransitions().get(0)).getWaypoints();
                        }
                    }
                }
                // logger.info("---流程線[" + sequenceFlow.getId() + "]不需要高亮顯示");
            }
            /*如果是定時任務獲取定時任務的座標*/
            if (!timeFlag) {
                waypoints = ((TransitionImpl) sequenceFlow).getWaypoints();
            }
            for (int i = 2; i < waypoints.size(); i += 2) { // waypoints.size() // minimally 4: x1, y1, // x2, y2
                boolean drawConditionalIndicator = (i == 2) && sequenceFlow.getProperty(BpmnParse.PROPERTYNAME_CONDITION) != null
                        && !((String) activity.getProperty("type")).toLowerCase().contains("gateway");
                if (i < waypoints.size() - 2) {
                    // 繪製一條流程線中不帶箭頭的直線部分
                    if (isHighLightedFlow) {

                        if (flag) {
                            processDiagramCanvas.drawHighLightSequenceflowWithoutArrow2(waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i),
                                    waypoints.get(i + 1), drawConditionalIndicator);
                        } else {
                            processDiagramCanvas.drawHighLightSequenceflowWithoutArrow(waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i),
                                    waypoints.get(i + 1), drawConditionalIndicator);
                        }
                    }
                } else {
                    // 繪製一條流程線中帶箭頭的直線部分
                    if (isHighLightedFlow) {
                        String name = null;
                        if (sequenceFlow.getProperty("name") != null) {
                            name = sequenceFlow.getProperty("name").toString();
                        }
                        if (flag) {
                            /*繪製紅色*/
                            processDiagramCanvas.drawHighLightSequenceflow2(waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i), waypoints.get(i + 1),
                                    drawConditionalIndicator);
                        } else {
                            /*繪製藍色*/
                            processDiagramCanvas.drawHighLightSequenceflow(waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i), waypoints.get(i + 1),
                                    drawConditionalIndicator);
                        }
                    }
                }
            }
            flowIndex++;
        }

        // Nested activities (boundary events)
        // 迴圈繪製當前節點下的子節點
        for (ActivityImpl nestedActivity : activity.getActivities()) {
            drawActivity(processDiagramCanvas, nestedActivity, highLightedActivities);
        }
    }

    /**
     * [繪製流程圖中的節點和節點上的流程線]
     *
     * @param processDiagramCanvas
     * @param activity
     * @param highLightedActivities
     * @Author: [Double]
     * @CreateDate: [2015-10-22]
     * @Version: [v2.0.0]
     */
    protected static void drawActivity(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity, List<String> highLightedActivities) throws Exception{
        // 獲取節點型別
        String type = (String) activity.getProperty("type");
        ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(type);
        if (drawInstruction != null) {

            drawInstruction.draw(processDiagramCanvas, activity);

            // Gather info on the multi instance marker
            boolean multiInstanceSequential = false, multiInstanceParallel = false, collapsed = false;
            String multiInstance = (String) activity.getProperty("multiInstance");
            if (multiInstance != null) {
                if ("sequential".equals(multiInstance)) {
                    multiInstanceSequential = true;
                } else {
                    multiInstanceParallel = true;
                }
            }

            // Gather info on the collapsed marker
            Boolean expanded = (Boolean) activity.getProperty(BpmnParse.PROPERTYNAME_ISEXPANDED);
            if (expanded != null) {
                collapsed = !expanded;
            }

            // Actually draw the markers
            processDiagramCanvas.drawActivityMarkers(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight(),
                    multiInstanceSequential, multiInstanceParallel, collapsed);

            // Draw highlighted activities
            // 如果高亮節點List中包含當前節點,則當前節點繪製為高亮樣式
//            logger.info("當前節點=【" + activity.getId() + "】");
//            logger.info("節點型別:[" + type + "]");

            if (highLightedActivities.contains(activity.getId())) {
                //最後一個節點高亮顯示(橙色)
                String id = "";
                if (highLightedActivities.get(highLightedActivities.size() - 1).equals(activity.getId())) {
                    //修改背景顏色
                    if (type.equals("userTask")) {
                        processDiagramCanvas.drawUserTask3((String) activity.getProperty("name"), activity.getX(), activity.getY(),
                                activity.getWidth(), activity.getHeight());
                        if (activity.getIncomingTransitions() != null) {
                            List<PvmTransition> incomingTransitions = activity.getIncomingTransitions();
                            for (PvmTransition p : incomingTransitions) {
                                if (p.getProperty("conditionText") != null) {
                                    if (p.getProperty("conditionText").toString().contains("不同意")) {
                                        if (highLightedActivities.get(highLightedActivities.size() - 2).equals(p.getSource().getId())) {
                                            ActivityImpl source = (ActivityImpl) p.getSource();
                                            String types = (String) source.getProperty("type");
                                            id = source.getId();
                                            processDiagramCanvas.drawExclusive(source.getX(), source.getY(), source.getWidth(), source.getHeight());
                                          /*  processDiagramCanvas.drawUserTask3((String) source.getProperty("name"), source.getX(), source.getY(),
                                                    activity.getWidth(), activity.getHeight());*/
                                        }

                                    }
                                }
                            }
                        }
                    }
                    drawCurrentActivityRed(processDiagramCanvas, activity);
                } else {
                    if (type.equals("userTask")) {
                        if (!id.equals(activity.getId())) {
                            processDiagramCanvas.drawUserTask2((String) activity.getProperty("name"), activity.getX(), activity.getY(),
                                    activity.getWidth(), activity.getHeight());
                        }
                    }
                    //已執行過的(高亮顯示藍色)
                    drawHighLight(processDiagramCanvas, activity);
                }
            } /*else {

                //未執行過的顯示深藍色(不包括定時邊界任務)
                if (!type.equals("exclusiveGateway") && !type.equals("boundaryTimer")) {
                    drawCurrentActivityRed2(processDiagramCanvas, activity);
                }

            }*/

        }

        // Outgoing transitions of activity

        for (PvmTransition sequenceFlow : activity.getOutgoingTransitions()) {

            List<Integer> waypoints = ((TransitionImpl) sequenceFlow).getWaypoints();
            for (int i = 2; i < waypoints.size(); i += 2) { // waypoints.size() // minimally 4: x1, y1, // x2, y2
                boolean drawConditionalIndicator = (i == 2) && sequenceFlow.getProperty(BpmnParse.PROPERTYNAME_CONDITION) != null
                        && !((String) activity.getProperty("type")).toLowerCase().contains("gateway");
                String name = null;
                if (sequenceFlow.getProperty("name") != null) {
                    name = sequenceFlow.getProperty("name").toString();
                }
                if (i < waypoints.size() - 2) {
                    // 繪製一條流程線中不帶箭頭的直線部分
                    processDiagramCanvas.drawSequenceflowWithoutArrow(name, waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i),
                            waypoints.get(i + 1), drawConditionalIndicator);
                } else {
                    processDiagramCanvas.drawSequenceflow(name, waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i), waypoints.get(i + 1),
                            drawConditionalIndicator);
                }
            }
        }

        // Nested activities (boundary events)
        // 迴圈繪製當前節點下的子節點
        for (ActivityImpl nestedActivity : activity.getActivities()) {
            drawActivity(processDiagramCanvas, nestedActivity, highLightedActivities);
        }
    }

    protected static String drawActivitys(ProcessDiagramCanvas processDiagramCanvas, ProcessDefinitionEntity processDefinition, List<String> highLightedActivities,String processInstanceId) throws Exception {
        // 獲取節點型別
        String id = "";
        //倒數第三以執行節點
        String id2 = "";
        for (ActivityImpl activity : processDefinition.getActivities()) {
            String type = (String) activity.getProperty("type");
            ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(type);
            if (drawInstruction != null) {
                if (!id.equals(activity.getId()) &&!id2.equals(activity.getId())) {
                    drawInstruction.draw(processDiagramCanvas, activity);
                }
                // Gather info on the multi instance marker
                boolean multiInstanceSequential = false, multiInstanceParallel = false, collapsed = false;
                String multiInstance = (String) activity.getProperty("multiInstance");
                if (multiInstance != null) {
                    if ("sequential".equals(multiInstance)) {
                        multiInstanceSequential = true;
                    } else {
                        multiInstanceParallel = true;
                    }
                }

                // Gather info on the collapsed marker
                Boolean expanded = (Boolean) activity.getProperty(BpmnParse.PROPERTYNAME_ISEXPANDED);
                if (expanded != null) {
                    collapsed = !expanded;
                }

                // Actually draw the markers
                processDiagramCanvas.drawActivityMarkers(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight(),
                        multiInstanceSequential, multiInstanceParallel, collapsed);


                if (highLightedActivities.contains(activity.getId())) {
                    //最後一個節點高亮顯示(橙色)
                    if (highLightedActivities.get(highLightedActivities.size() - 1).equals(activity.getId())) {//是否是最後一個節點
                        //修改背景顏色
                        //判斷是否使用者節點
                        if (type.equals("userTask")) {
                            //傳入引數  processInstanceId 流程例項id和taskDefKey 已完成流程id
                            processDiagramCanvas.putParamMap("processInstanceId",processInstanceId);
                            processDiagramCanvas.putParamMap("taskDefKey",activity.getId());
                            //修改單前節點背景為橙色
                            processDiagramCanvas.drawUserTask3((String) activity.getProperty("name"), activity.getX(), activity.getY(),
                                    activity.getWidth(), activity.getHeight());
                            //判斷單前節點的上一個節點是否為空
                            if (activity.getIncomingTransitions() != null) {
                                //獲取當前節點的所有上一個節點
                                List<PvmTransition> incomingTransitions = activity.getIncomingTransitions();
                                for (PvmTransition p : incomingTransitions) {
                                    if (p.getProperty("conditionText") != null) {
                                        //判斷是否是不同意來源
                                        if (p.getProperty("conditionText").toString().contains("不同意")) {
                                            //判斷以執行節點中倒數第二個是否包含當前節點的上一個節點
                                            if (highLightedActivities.get(highLightedActivities.size() - 2).equals(p.getSource().getId())) {
                                                //型別轉換
                                                ActivityImpl source = (ActivityImpl) p.getSource();
                                                String types = (String) source.getProperty("type");
                                                id = source.getId();
                                                //判斷是否是排他閘道器
                                                if(types.equals("exclusiveGateway")||types.equals("parallelGateway")||types.equals("inclusiveGateway")){
                                                    //繪製當前節點的上一個節點為紅色
                                                    drawHighLight3(processDiagramCanvas, source);
                                                }
                                                //通過當前節點的上個一節點找到上個節點上所有來源
                                                List<PvmTransition> incomingTransitions1 = source.getIncomingTransitions();
                                                for (PvmTransition pt : incomingTransitions1) {
                                                    ActivityImpl source1 = (ActivityImpl) pt.getSource();
                                                    //找到倒數第三個節點
                                                    if (highLightedActivities.get(highLightedActivities.size() - 3).equals(source1.getId())) {

                                                        id2=drawUserTaskAndUser(processDiagramCanvas,source1,processInstanceId);

                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        //修改單前節點邊框為橙色
                        drawCurrentActivityRed(processDiagramCanvas, activity);
                    } else {//如果不是最後一個節點
                        if (type.equals("userTask")) { //type 是否為 userTask
                            if (!id.equals(activity.getId()) && !id2.equals(activity.getId())) {
                                //傳入引數  processInstanceId 流程例項id和taskDefKey 已完成流程id
                                processDiagramCanvas.putParamMap("processInstanceId",processInstanceId);
                                processDiagramCanvas.putParamMap("taskDefKey",activity.getId());
                                processDiagramCanvas.drawUserTask2((String) activity.getProperty("name"), activity.getX(), activity.getY(),
                                        activity.getWidth(), activity.getHeight());
                            }
                        }
                        //已執行過的(高亮顯示藍色)
                        if (!id.equals(activity.getId()) && !id2.equals(activity.getId())) {
                            if(type.equals("exclusiveGateway")||type.equals("parallelGateway")||type.equals("inclusiveGateway")){
                                drawHighLight2(processDiagramCanvas, activity);
                            }else{
                            drawHighLight(processDiagramCanvas, activity);}
                        }

                    }
                }
            }

            for (PvmTransition sequenceFlow : activity.getOutgoingTransitions()) {

                List<Integer> waypoints = ((TransitionImpl) sequenceFlow).getWaypoints();
                for (int i = 2; i < waypoints.size(); i += 2) { // waypoints.size() // minimally 4: x1, y1, // x2, y2
                    boolean drawConditionalIndicator = (i == 2) && sequenceFlow.getProperty(BpmnParse.PROPERTYNAME_CONDITION) != null
                            && !((String) activity.getProperty("type")).toLowerCase().contains("gateway");
                    String name = null;
                    if (sequenceFlow.getProperty("name") != null) {
                        name = sequenceFlow.getProperty("name").toString();
                    }
                    if (i < waypoints.size() - 2) {
                        // 繪製一條流程線中不帶箭頭的直線部分
                        if (i >= 4) {
                            name = null;
                        }
                        //繪製線條增加顯示名稱
                        processDiagramCanvas.drawSequenceflowWithoutArrow(name, waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i),
                                waypoints.get(i + 1), drawConditionalIndicator);
                    } else {
                        if (waypoints.size() > 4) {
                            name = null;
                        }
                        //繪製線條增加顯示名稱
                        processDiagramCanvas.drawSequenceflow(name, waypoints.get(i - 2), waypoints.get(i - 1), waypoints.get(i), waypoints.get(i + 1),
                                drawConditionalIndicator);
                    }
                }
            }

            // Nested activities (boundary events)
            // 迴圈繪製當前節點下的子節點
            for (ActivityImpl nestedActivity : activity.getActivities()) {
                drawActivity(processDiagramCanvas, nestedActivity, highLightedActivities);
            }
        }
        return id2;
    }

    private static void drawHighLight(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity) {
        processDiagramCanvas.drawHighLight(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight());
    }

    private static String drawUserTaskAndUser(ProcessDiagramCanvas processDiagramCanvas,ActivityImpl source1,String processInstanceId) throws Exception{
        String types1 = (String) source1.getProperty("type");
        String  id2 = source1.getId();
        if(types1.equals("userTask")) {
            //傳入引數  processInstanceId 流程例項id和taskDefKey 當前流程id
            processDiagramCanvas.putParamMap("processInstanceId",processInstanceId);
            processDiagramCanvas.putParamMap("taskDefKey",source1.getId());
            //修改背景顏色
            processDiagramCanvas.drawUserTask4((String) source1.getProperty("name"), source1.getX(), source1.getY(),
                    source1.getWidth(), source1.getHeight());
            //修改邊框顏色
            processDiagramCanvas.drawExclusive(source1.getX(), source1.getY(), source1.getWidth(), source1.getHeight());
        }else if(types1.equals("exclusiveGateway")||types1.equals("parallelGateway")||types1.equals("inclusiveGateway")){
            drawHighLight3(processDiagramCanvas, source1);
        }
        return id2;
    }
    /**
     * [繪製高亮顯示的節點]
     *
     * @param processDiagramCanvas
     * @param activity
     * @Author: [Double]
     * @CreateDate: [2015-10-22]
     * @Version: [v2.0.0]
     */
    private static void drawHighLight2(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity) {
        processDiagramCanvas.drawHighLight2(activity,activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight());
    }
    private static void drawHighLight3(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity) {
        processDiagramCanvas.drawHighLight3(activity,activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight());
    }
    /**
     * [繪製當前節點紅色顯示]
     *
     * @param processDiagramCanvas
     * @param activity
     * @Author: [Double]
     * @CreateDate: [2015-10-22]
     * @Version: [v2.0.0]
     */
    private static void drawCurrentActivityRed(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity) {
        processDiagramCanvas.drawHighLightRed(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight());
    }

    private static void drawCurrentActivityRed2(ProcessDiagramCanvas processDiagramCanvas, ActivityImpl activity) {
        processDiagramCanvas.drawHighLightRed2(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight());
    }

    public static ProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel) {
        // We need to calculate maximum values to know how big the image will be in its entirety
        double minX = Double.MAX_VALUE;
        double maxX = 0;
        double minY = Double.MAX_VALUE;
        double maxY = 0;

        for (Pool pool : bpmnModel.getPools()) {
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
            minX = graphicInfo.getX();
            maxX = graphicInfo.getX() + graphicInfo.getWidth();
            minY = graphicInfo.getY();
            maxY = graphicInfo.getY() + graphicInfo.getHeight();
        }

        List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);

        for (FlowNode flowNode : flowNodes) {
            GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            // width
            if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
                maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
            }
            if (flowNodeGraphicInfo.getX() < minX) {
                minX = flowNodeGraphicInfo.getX();
            }
            // height
            if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
                maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
            }
            if (flowNodeGraphicInfo.getY() < minY) {
                minY = flowNodeGraphicInfo.getY();
            }

            for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
                List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
                for (GraphicInfo graphicInfo : graphicInfoList) {
                    // width
                    if (graphicInfo.getX() > maxX) {
                        maxX = graphicInfo.getX();
                    }
                    if (graphicInfo.getX() < minX) {
                        minX = graphicInfo.getX();
                    }
                    // height
                    if (graphicInfo.getY() > maxY) {
                        maxY = graphicInfo.getY();
                    }
                    if (graphicInfo.getY() < minY) {
                        minY = graphicInfo.getY();
                    }
                }
            }
        }

        int nrOfLanes = 0;
        for (Process process : bpmnModel.getProcesses()) {
            for (org.activiti.bpmn.model.Lane l : process.getLanes()) {

                nrOfLanes++;

                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
                // // width
                if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
                    maxX = graphicInfo.getX() + graphicInfo.getWidth();
                }
                if (graphicInfo.getX() < minX) {
                    minX = graphicInfo.getX();
                }
                // height
                if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
                    maxY = graphicInfo.getY() + graphicInfo.getHeight();
                }
                if (graphicInfo.getY() < minY) {
                    minY = graphicInfo.getY();
                }
            }
        }

        // Special case, see http://jira.codehaus.org/browse/ACT-1431
        if (flowNodes.size() == 0 && bpmnModel.getPools().size() == 0 && nrOfLanes == 0) {
            // Nothing to show
            minX = 0;
            minY = 0;
        }

        return new ProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY);
    }

    protected static List<FlowNode> gatherAllFlowNodes(FlowElementsContainer flowElementsContainer) {
        List<FlowNode> flowNodes = new ArrayList<FlowNode>();

        for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
            if (flowElement instanceof FlowNode) {
                flowNodes.add((FlowNode) flowElement);
            }

            if (flowElement instanceof FlowElementsContainer) {
                flowNodes
                        .addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement));
            }
        }

        return flowNodes;
    }

    protected static List<FlowNode> gatherAllFlowNodes(BpmnModel bpmnModel) {
        List<FlowNode> flowNodes = new ArrayList<FlowNode>();

        for (Process process : bpmnModel.getProcesses()) {
            flowNodes.addAll(gatherAllFlowNodes(process));
        }

        return flowNodes;
    }


}

--------------------------------------------------------------------------------------------------------------------------------------------

import com.cfne.cuckoo.oa.domain.entity.category.FlowCategoryEntity;
import com.cfne.cuckoo.oa.service.category.FlowCategoryService;
import com.cfne.cuckoo.oa.service.flow.history.ActHiTaskinstService;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.util.IoUtil;
import org.activiti.engine.impl.util.ReflectUtil;
import org.apache.commons.collections.map.HashedMap;
import org.apache.poi.ss.formula.functions.T;
import sun.rmi.runtime.Log;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;

/**
 */
public class ProcessDiagramCanvas extends  BaseProcessDiagramCanvas{

    protected static final Logger LOGGER = Logger.getLogger(ProcessDiagramCanvas.class.getName());

    // Predefined sized
    protected static final int ARROW_WIDTH = 5;

    protected static final int CONDITIONAL_INDICATOR_WIDTH = 16;

    protected static final int MARKER_WIDTH = 12;

    // Colors
    protected static Color TASK_COLOR = new Color(255, 255, 255);//節點背景顏色19, 185, 156
/*
    protected static Color BOUNDARY_EVENT_COLOR = new Color(13, 179, 166);

   protected static Color CONDITIONAL_INDICATOR_COLOR = new Color(13, 179, 166);

   protected static Color HIGHLIGHT_COLOR = new Color(13, 179, 166); //Color.CYAN;*/


    protected static Color BOUNDARY_EVENT_COLOR = new Color(19, 185, 156);//邊界事件背景顏色顏色

    protected static Color CONDITIONAL_INDICATOR_COLOR = new Color(19, 185, 156);//已執行線條顏色

    protected static Color HIGHLIGHT_COLOR = new Color(19, 185, 156); //Color.CYAN;已執行節點顏色
    protected static Color HIGHLIGHT_RED = new Color(255, 0, 0); //Color.CYAN;已執行b被打回顏色
    /*protected static Color HIGHLIGHT_COLOR_RED = new Color(166, 131, 65);  // Color.YELLOW;*/
    protected static Color HIGHLIGHT_COLOR_RED = new Color(230, 130, 70);  // 流程走到該節點的邊框顏色;
    protected static Color HIGHLIGHT_COLOR_RED2 = new Color(0, 0, 0);  // 流程未走過的顏色
    // Strokes
    protected static Stroke THICK_TASK_BORDER_STROKE = new BasicStroke(3.0f);

    protected static Stroke GATEWAY_TYPE_STROKE = new BasicStroke(3.0f);

    protected static Stroke END_EVENT_STROKE = new BasicStroke(3.0f);

    protected static Stroke MULTI_INSTANCE_STROKE = new BasicStroke(1.3f);

    protected static Stroke EVENT_SUBPROCESS_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[]{1.0f},
            0.0f);

    // icons
    protected static int ICON_SIZE = 16;

    protected static Image USERTASK_IMAGE;

    protected static Image SCRIPTTASK_IMAGE;

    protected static Image SERVICETASK_IMAGE;

    protected static Image RECEIVETASK_IMAGE;

    protected static Image SENDTASK_IMAGE;

    protected static Image MANUALTASK_IMAGE;

    protected static Image BUSINESS_RULE_TASK_IMAGE;

    protected static Image TIMER_IMAGE;

    protected static Image ERROR_THROW_IMAGE;

    protected static Image ERROR_CATCH_IMAGE;

    protected static Image SIGNAL_CATCH_IMAGE;

    protected static Image SIGNAL_THROW_IMAGE;

    // icons are statically loaded for performace
    static {
        try {
           /* ImageIO.read(this.getClass().getClassLoader().getResourceAsStream("stencilset.json"));*/
            USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/user.png"));
            SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/script.png"));
            SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/service.png"));
            RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/receive.png"));
            SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/send.png"));
            MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/manual.png"));
            BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/business_rule.png"));
            TIMER_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/timer.png"));
            ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/error_throw.png"));
            ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/error_catch.png"));
            SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/signal_catch.png"));
            SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/engine/impl/bpmn/deployer/signal_throw.png"));
        } catch (IOException e) {
            LOGGER.warning("Could not load image for process diagram creation: " + e.getMessage());
        }
    }

    protected int canvasWidth = -1;

    protected int canvasHeight = -1;

    protected int minX = -1;

    protected int minY = -1;

    protected BufferedImage processDiagram;

    protected Graphics2D g;

    protected FontMetrics fontMetrics;

    protected boolean closed;

    /**
     * Creates an empty canvas with given width and height.
     */
    public ProcessDiagramCanvas(int width, int height) {
        this.canvasWidth = width;
        this.canvasHeight = height;
        this.processDiagram = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        this.g = processDiagram.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setPaint(Color.BLACK);

        Font font = new Font("宋體", Font.BOLD, 11);
        g.setFont(font);
        this.fontMetrics = g.getFontMetrics();
    }

    /**
     * Creates an empty canvas with given width and height.
     * <p>
     * Allows to specify minimal boundaries on the left and upper side of the
     * canvas. This is useful for diagrams that have white space there (eg
     * Signavio). Everything beneath these minimum values will be cropped.
     *
     * @param minX Hint that will be used when generating the image. Parts that fall
     *             below minX on the horizontal scale will be cropped.
     * @param minY Hint that will be used when generating the image. Parts that fall
     *             below minX on the horizontal scale will be cropped.
     */
    public ProcessDiagramCanvas(int width, int height, int minX, int minY) {
        this(width, height);
        this.minX = minX;
        this.minY = minY;
    }

    /**
     * Generates an image of what currently is drawn on the canvas.
     * <p>
     * Throws an {@link ActivitiException} when {@link #close()} is already
     * called.
     */
    public InputStream generateImage(String imageType) {
        if (closed) {
            throw new ActivitiException("ProcessDiagramGenerator already closed");
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            // Try to remove white space
            minX = (minX <= 5) ? 5 : minX;
            minY = (minY <= 5) ? 5 : minY;
            BufferedImage imageToSerialize = processDiagram;
            if (minX >= 0 && minY >= 0) {
                imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5);
            }
            ImageIO.write(imageToSerialize, imageType, out);
        } catch (IOException e) {
            throw new ActivitiException("Error while generating process image", e);
        } finally {
            IoUtil.closeSilently(out);
        }
        return new ByteArrayInputStream(out.toByteArray());
    }

    /**
     * Closes the canvas which dissallows further drawing and releases graphical
     * resources.
     */
    public void close() {
        g.dispose();
        closed = true;
    }

    public void drawNoneStartEvent(int x, int y, int width, int height) {
        drawStartEvent(x, y, width, height, null);
    }

    public void drawTimerStartEvent(int x, int y, int width, int height) {
        drawStartEvent(x, y, width, height, TIMER_IMAGE);
    }

    public void drawStartEvent(int x, int y, int width, int height, Image image) {
        g.draw(new Ellipse2D.Double(x, y, width, height));
        if (image != null) {
            g.drawImage(image, x, y, width, height, null);
        }

    }

    public void drawNoneEndEvent(int x, int y, int width, int height) {
        Stroke originalStroke = g.getStroke();
        g.setStroke(END_EVENT_STROKE);
        g.draw(new Ellipse2D.Double(x, y, width, height));
        g.setStroke(originalStroke);
    }

    public void drawErrorEndEvent(int x, int y, int width, int height) {
        drawNoneEndEvent(x, y, width, height);
        g.drawImage(ERROR_THROW_IMAGE, x + 3, y + 3, width - 6, height - 6, null);
    }

    public void drawErrorStartEvent(int x, int y, int width, int height) {
        drawNoneStartEvent(x, y, width, height);
        g.drawImage(ERROR_CATCH_IMAGE, x + 3, y + 3, width - 6, height - 6, null);
    }

    public void drawCatchingEvent(int x, int y, int width, int height, Image image) {
        // event circles
        Ellipse2D outerCircle = new Ellipse2D.Double(x, y, width, height);
        int innerCircleX = x + 3;
        int innerCircleY = y + 3;
        int innerCircleWidth = width - 6;
        int innerCircleHeight = height - 6;
        Ellipse2D innerCircle = new Ellipse2D.Double(innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight);

        Paint originalPaint = g.getPaint();
        g.setPaint(BOUNDARY_EVENT_COLOR);
        g.fill(outerCircle);

        g.setPaint(originalPaint);
        g.draw(outerCircle);
        g.draw(innerCircle);

        g.drawImage(image, innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight, null);
    }

    public void drawCatchingTimerEvent(int x, int y, int width, int height) {
        drawCatchingEvent(x, y, width, height, TIMER_IMAGE);
    }

    public void drawCatchingErroEvent(int x, int y, int width, int height) {
        drawCatchingEvent(x, y, width, height, ERROR_CATCH_IMAGE);
    }

    public void drawCatchingSignalEvent(int x, int y, int width, int height) {
        drawCatch