原生js实现Canvas实现拖拽式绘图,支持画笔、线条、箭头、三角形和圆形等等图形绘制功能

栏目: Html5 · 发布时间: 5年前

内容简介:一、实现的功能1、基于oop思想构建,支持坐标点、线条(由坐标点组成,包含方向)、多边形(由多个坐标点组成)、圆形(包含圆心坐标点和半径)等实体2、原生JavaScript实现,不依赖任何第三方js库和插件

一、实现的功能

1、基于oop思想构建,支持坐标点、线条(由坐标点组成,包含方向)、多边形(由多个坐标点组成)、圆形(包含圆心坐标点和半径)等实体

2、原生JavaScript实现,不依赖任何第三方js库和插件

3、多图形绘制(支持画笔、线条、箭头、三角形、矩形、平行四边形、梯形以及多边形和圆形绘制)

4、拖拽式绘制(鼠标移动过程中不断进行canvas重绘)

5、图片绘制(作为背景图片时重绘会发生闪烁现象,暂时有点问题,后面继续完善)

5、清空绘制功能

6、新版本优化绘制性能(使用共享坐标变量数组,减少了大量的对象创建操作)

7、新版本支持箭头绘制功能

二、完整实现代码

DrawingTools =(function(){

//公共方法
            var getDom=function(id){return document.getElementById(id)};
            var isNull=function(s){return s==undefined||typeof(s)=='undefined'||s==null||s=='null'||s==''||s.length<1};
            var hideDefRM=function(){document.oncontextmenu=function(){return false}};//屏蔽浏览器默认鼠标事件
            /**绘图容器*/
            var cbtCanvas;
            /**绘图对象*/
            var cxt;
            /**绘制的图形列表*/
            var shapes=new Array();

            var graphkind={'cursor':0,'pen':1,'line':2,'trian':3,'rect':4,'poly':5,'circle':6,'arrow':21,'parallel':41,'trapezoid':42};
            //背景图片绘制配置
            var bgPictureConfig={
                pic:null,//背景图片地址或路径
                repaint:true,//是否作为永久背景图,每次清除时会进行重绘
            };
            //加载并绘制图片(src:图片路径或地址),默认重绘背景图
            var loadPicture=function(src){
                if(isNull(bgPictureConfig.repaint)||bgPictureConfig.repaint){bgPictureConfig.pic=src}
                var img = new Image();
                img.onload = function(){cxt.drawImage(img,0,0)}
                img.src =src;
            }

            //绘图基础配置
            var paintConfig={lineWidth:1,//线条宽度,默认1
                strokeStyle:'red',//画笔颜色,默认红色
                fillStyle:'red',//填充色
                lineJoin:"round",//线条交角样式,默认圆角
                lineCap:"round",//线条结束样式,默认圆角
            };
            //重新载入绘制样式
            var resetStyle=function(){
                cxt.strokeStyle=paintConfig.strokeStyle;
                cxt.lineWidth=paintConfig.lineWidth;
                cxt.lineJoin=paintConfig.lineJoin;
                cxt.lineCap=paintConfig.lineCap;
                cxt.fillStyle=paintConfig.fillStyle;
            }

            //鼠标图形
            var cursors=['crosshair','pointer'];
            /** 切换鼠标样式*/
            var switchCorser=function(b){
                cbtCanvas.style.cursor=((isNull(b)?isDrawing():b)?cursors[0]:cursors[1]);
            }
            //操作控制变量组
            var ctrlConfig={
                kind:0,//当前绘画分类
                isPainting:false,//是否开始绘制
                startPoint:null,//起始点
                cuGraph:null,//当前绘制的图像
                cuPoint:null,//当前临时坐标点,确定一个坐标点后重新构建
                cuAngle:null,//当前箭头角度
                vertex:[],//坐标点
            }
            /**获取当前坐标点*/
            var getCuPoint=function(i){
                return ctrlConfig.cuPoint[i];
            }
            /**设置当前坐标点*/
            var setCuPoint=function(p,i){
                return ctrlConfig.cuPoint[i]=p;
            }
            /**设置当前临时坐标点值*/
            var setCuPointXY=function(x,y,i){
                if(isNull(ctrlConfig.cuPoint)){
                    var arr=new Array();
                    arr[i]=new Point(x,y);
                    ctrlConfig.cuPoint=arr;
                }else if(isNull(ctrlConfig.cuPoint[i])){
                    setCuPoint(new Point(x,y),i);
                }else{
                    ctrlConfig.cuPoint[i].setXY(x,y);
                }
                return getCuPoint(i);
            }

            /**是否正在绘制*/
            var isDrawing=function (){
                return ctrlConfig.isPainting;
            }
            /**开始绘制状态*/
            var beginDrawing=function(){
                ctrlConfig.isPainting=true;
            }
            /**结束绘制状态*/
            var stopDrawing=function(){
                ctrlConfig.isPainting=false;
            }
            /**是否有开始坐标点*/
            var hasStartPoint=function(){
                return !isNull(ctrlConfig.startPoint);
            }
            /**设置当前绘制的图形*/
            var setCuGraph=function(g){
                ctrlConfig.cuGraph=g;
            }
            /**获取当前绘制的图形*/
            var getCuGraph=function(){
                return ctrlConfig.cuGraph;
            }
            /**设置开始坐标点(线条的起始点,三角形的顶点,圆形的圆心,四边形的左上角或右下角,多边形的起始点)*/
            var setStartPoint=function(p){
                ctrlConfig.startPoint=p;
            }
            /**获取开始坐标点*/
            var getStartPoint=function(){
                return ctrlConfig.startPoint;
            }

            /**清空全部*/
            var clearAll=function(){
                cxt.clearRect(0,0,cbtCanvas.width,cbtCanvas.height);
            }
            /**重绘*/
            var repaint=function(){
                clearAll();
                /*
                if(bgPictureConfig.repaint){
                    loadPicture(bgPictureConfig.pic);
                }*/
            }

            /**点(坐标,绘图的基本要素,包含x,y坐标)*/
            var Point=(function(x1,y1){
                var x=x1,y=y1;
                return{
                    set:function(p){
                        x=p.x,y=p.y;
                    },
                    setXY:function(x2,y2){
                        x=x2;y=y2;
                    },
                    setX:function(x3){
                        x=x3;
                    },
                    setY:function(y3){
                        y=y3;
                    },
                    getX:function(){
                        return x;
                    },
                    getY:function(){
                        return y;
                    }
                }
            });
            /**多角形(三角形、矩形、多边形),由多个点组成*/
            var Poly=(function(ps1){
                var ps=isNull(ps1)?new Array():ps1;
                var size=ps.length;
                return{
                    set:function(ps2){
                        ps=ps2;
                    },
                    getSize:function(){
                        return size;
                    },
                    setPoint:function(p,i){
                        if(isNull(p)&&isNaN(i)){
                            return;
                        }
                        ps[i]=p;
                    },
                    setStart:function(p1){
                        if(isNull(ps)){
                            ps=new Array();
                            return ps.push(p1);
                        }else{
                            ps[0]=p1;
                        }
                    },
                    add:function(p){
                        if(isNull(ps)){
                            ps=new Array();
                        }
                        return ps.push(p);
                    },
                    pop:function(){
                        if(isNull(ps)){
                            return;
                        }
                        return ps.pop();
                    },
                    shift:function(){
                        if(isNull(ps)){
                            return;
                        }
                        return ps.shift;
                    },
                    get:function(){
                        if(isNull(ps)){
                            return null;
                        }
                        return ps;
                    },
                    draw:function(){
                        cxt.beginPath();
                        for(i in ps){
                            if(i==0){
                                cxt.moveTo(ps[i].getX(),ps[i].getY());
                            }else{
                                cxt.lineTo(ps[i].getX(),ps[i].getY());
                            }
                        }
                        cxt.closePath();
                        cxt.stroke();
                    }
                }
            });
            /*线条(由两个点组成,包含方向)*/
            var Line=(function(p1,p2,al){
                var start=p1,end=p2,angle=al;

                var drawLine=function(){
                    cxt.beginPath();
                    cxt.moveTo(p1.getX(),p1.getY());
                    cxt.lineTo(p2.getX(),p2.getY());
                    cxt.stroke();
                }
                //画箭头
                var drawArrow=function() {
                    var vertex =ctrlConfig.vertex;
                    var x1=p1.getX(),y1=p1.getY(),x2=p2.getX(),y2=p2.getY();
                    var el=50,al=15;
                    //计算箭头底边两个点(开始点,结束点,两边角度,箭头角度)
                    vertex[0] = x1,vertex[1] = y1, vertex[6] = x2,vertex[7] = y2;
                    //计算起点坐标与X轴之间的夹角角度值
                    var angle = Math.atan2(y2 - y1, x2 - x1) / Math.PI * 180;
                    var x = x2 - x1,y = y2 - y1,length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
                    if (length < 250) {
                        el/=2,al/2;
                    }else if(length<500){
                        el*=length/500,al*=length/500;
                    }
                    vertex[8] = x2 - el * Math.cos(Math.PI / 180 * (angle + al));
                    vertex[9] = y2- el * Math.sin(Math.PI / 180 * (angle + al));
                    vertex[4] = x2- el* Math.cos(Math.PI / 180 * (angle - al));
                    vertex[5] = y2 - el * Math.sin(Math.PI / 180 * (angle - al));
                    //获取另外两个顶点坐标
                    x=(vertex[4]+vertex[8])/2,y=(vertex[5]+vertex[9])/2;
                    vertex[2] = (vertex[4] + x) / 2;
                    vertex[3] = (vertex[5] + y) / 2;
                    vertex[10] = (vertex[8] +x) / 2;
                    vertex[11] = (vertex[9] +y) / 2;
                    //计算完成,开始绘制
                    cxt.beginPath();
                    cxt.moveTo(vertex[0], vertex[1]);
                    cxt.lineTo(vertex[2], vertex[3]);
                    cxt.lineTo(vertex[4], vertex[5]);
                    cxt.lineTo(vertex[6], vertex[7]);
                    cxt.lineTo(vertex[8], vertex[9]);
                    cxt.lineTo(vertex[10], vertex[11]);
                    cxt.closePath();
                    cxt.fill();
                    cxt.stroke();
                }
                return{
                    setStart:function(s){
                        start=s;
                    },
                    setEnd:function(e){
                        end=e;
                    },
                    getStart:function(){
                        return start;
                    },
                    getEnd:function(){
                        return end;
                    },
                    draw:function(){
                        if(angle){
                            drawArrow();
                        }else{
                            drawLine();
                        }
                    }
                }
            });
            /**圆形(包含圆心点和半径)*/
            var Circle=(function(arr){
                //包含起始点(圆心)和结束点,以及圆半径
                var startPoint=arr.start,endPoint=arr.end,radius=arr.radius;
                /*绘制圆*/
                var drawCircle=function(){
                    cxt.beginPath();
                    var x=startPoint.getX();
                    var y=startPoint.getY();
                    if(isNull(radius)){
                        radius=calculateRadius(startPoint,endPoint);
                    }
                    //x,y,半径,开始点,结束点,顺时针/逆时针
                    cxt.arc(x,y,radius,0,Math.PI*2,false); // 绘制圆
                    cxt.stroke();
                }
                //计算圆半径
                var calculateRadius=function(p1,p2){
                    var width=p2.getX()-p1.getX();
                    var height=p2.getY()-p1.getY();
                    //如果是负数
                    if(width<0||height<0){
                        width=Math.abs(width);
                    }
                    //计算两点距离=平方根(width^2+height^2)
                    c=Math.sqrt(Math.pow(width,2)+Math.pow(height,2));
                    return c;
                }
                return{
                    set:function(params){
                        startPoint=params.start;
                        endPoint=params.end;
                        radius=params.radius;
                    },
                    setPoint:function(p1){
                        p=p1;
                    },
                    getPoint:function(){
                        return p;
                    },
                    setRadius:function(r1){
                        radius=r1;
                    },
                    getRadius:function(){
                        return radius;
                    },
                    calcRadius:calculateRadius,
                    //绘制
                    draw:drawCircle,
                }
            });
            /**绘制线条 工具 方法*/
            var drawLine=function(p){
                cxt.beginPath();
                cxt.moveTo(startPosition.getX(),startPosition.getY());
                cxt.lineTo(p.getX(),p.getY());
                cxt.stroke();
            }

            /**绘制三角形工具方法*/
            var drawTrian=function(ps){
                cxt.beginPath();
                var a=ps.get();
                cxt.moveTo(a[0].getX(),a[0].getY());
                cxt.lineTo(a[1].getX(),a[1].getY());
                cxt.lineTo(a[2].getX(),a[2].getY());
                cxt.closePath();
                cxt.stroke();
            }

            /**绘制矩形工具方法*/
            var drawRect=function(p2){
                var p=getStartPoint();
                var width=p.getX()-p2.getX();
                var height=p.getY()-p2.getY();
                cxt.beginPath();
                cxt.strokeRect(x,y,width,height);//绘制矩形
            }

            /*绘制多边形工具方法*/
            var drawpolygon=function(ps){
                if(ps.length>1){//保证只有两个坐标点才是矩形
                    cxt.beginPath();
                    var p=ctrlConfig.startPoint;
                    var x=p.getX();
                    var y=p.getY();
                    cxt.moveTo(x,y);
                    for(p1 in ps){
                        cxt.lineTo(p1.getX(),p1.getY());
                    }
                    cxt.stroke();
                }
            }

            /*绘制圆角矩形工具方法*/
            var  drawRoundedRect=function(x,y,width,height,radius){
                cxt.beginPath();
                cxt.moveTo(x,y+radius);
                cxt.lineTo(x,y+height-radius);
                cxt.quadraticCurveTo(x,y+height,x+radius,y+height);
                cxt.lineTo(x+width-radius,y+height);
                cxt.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
                cxt.lineTo(x+width,y+radius);
                cxt.quadraticCurveTo(x+width,y,x+width-radius,y);
                cxt.lineTo(x+radius,y);
                cxt.quadraticCurveTo(x,y,x,y+radius);
                cxt.stroke();
            }
            /*绘制圆工具方法*/
            var drawCircle=function(c){
                var p=c.getPoint();//坐标点
                var x=p.getX();
                var y=p.getY();
                var r=c.getRadius();
                cxt.beginPath();
                //x,y,半径,开始点,结束点,顺时针/逆时针
                cxt.arc(x,y,r,0,Math.PI*2,false); // 绘制圆
                cxt.stroke();
            }
            //计算圆半径工具方法
            var calculateRadius=function(p1,p2){
                var width=p2.getX()-p1.getX();
                var height=p2.getY()-p1.getY();
                //如果是负数
                if(width<0||height<0){
                    width=Math.abs(width);
                }
                //计算两点距离=平方根(width^2+height^2)
                c=Math.sqrt(Math.pow(width,2)+Math.pow(height,2));
                return c;
            }

            //鼠标按键点击(首次点击确定开始坐标点,拖动鼠标不断进行图形重绘)
            var mouseDown = function(e){
                var btnNum = e.button;
                if(btnNum==0){
                    console.log("选择:"+ctrlConfig.kind);
                    //设置起始点
                    switch(ctrlConfig.kind){
                        case graphkind.pen://画笔(不松开鼠标按键一直画)
                            beginDrawing();//开始绘制
                            cxt.beginPath();
                            cxt.moveTo(e.offsetX,e.offsetY);
                            break;
                        case graphkind.poly://多边形
                            var p=new Point(e.offsetX,e.offsetY);
                            if(isDrawing()){
                                getCuGraph().add(p);//添加到
                            }else{//第一次确定开始坐标
                                beginDrawing();//开始绘制
                                setStartPoint(p);
                                var poly=new Poly();
                                poly.add(p);
                                setCuGraph(poly);//设置当前绘制图形
                            }
                            break;
                        case graphkind.line://线条
                        case graphkind.arrow://方向
                        case graphkind.trian://三角形
                        case graphkind.rect://矩形
                        case graphkind.parallel://平行四边形
                        case graphkind.trapezoid://梯形
                            beginDrawing();//开始绘制
                            var p=new Point(e.offsetX,e.offsetY);
                            setStartPoint(p);
                            var poly=new Poly();
                            poly.add(p);
                            setCuGraph(poly);//设置当前绘制图形
                            break;
                        case graphkind.circle://圆
                            console.log("确定图形绘制开始坐标点:"+e.offsetX+","+e.offsetY);//点击确定图形的开始坐标点
                            beginDrawing();//开始绘制
                            var p=new Point(e.offsetX,e.offsetY);
                            setStartPoint(p);
                            var circle= new Circle({'start':p});
                            setCuGraph(circle);
                            break;
                        case ctrlConfig.cursor: //手型鼠标
                        default://默认是手型鼠标,不允许绘制
                    }
                }else if(btnNum==2){
                    console.log("右键由于结束多边形绘制");
                    if(isDrawing()){
                        if(ctrlConfig.kind==graphkind.poly){
                            repaint();
                            getCuGraph().draw();
                            stopDrawing();//结束绘制
                        }
                    }
                }
                hideDefRM();//屏蔽浏览器默认事件
            }
            //鼠标移动(拖动,根据鼠标移动的位置不断重绘图形)
            var mouseMove = function(e){
                if(isDrawing()&&hasStartPoint()){//检查是否开始绘制,检查是否有开始坐标点
                    //画笔不需要重绘
                    if(ctrlConfig.kind>1){
                        repaint();//重绘
                    }
                    var p=setCuPointXY(e.offsetX,e.offsetY,0);//设置共享的临时坐标点,用于防止重复创建对象
                    switch(ctrlConfig.kind){
                        case graphkind.pen://画笔(一直画)
                            cxt.lineTo(e.offsetX,e.offsetY);
                            cxt.stroke();
                            break;
                        case graphkind.poly://多边形
                            var poly=getCuGraph(poly);
                            var size=poly.getSize();
                            poly.setPoint(p,(size-1));
                            poly.draw();
                            break;
                        case graphkind.line://线条
                            var line=new Line(getStartPoint(),p,false);
                            ctrlConfig.cuGraph=line;
                            line.draw();
                            break;
                        case graphkind.arrow://方向
                            var line=new Line(getStartPoint(),p,true);
                            ctrlConfig.cuGraph=line;
                            line.draw();
                            break;
                        case graphkind.trian://三角形
                            var lu=getStartPoint();
                            var x2=p.getX();
                            var x1=lu.getX();
                            //三角形左边的点坐标计算方法:(x1-(x2-x1),y2)
                            var x3=x1-(x2-x1);
                            var l=setCuPointXY(x3,p.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象
                            var poly=getCuGraph();//获取当前图形
                            poly.set([lu,p,l]);
                            poly.draw();//即时绘制
                            break;
                        case graphkind.parallel://平行四边形
                            var lu=getStartPoint();
                            var x3=p.getX();
                            var x1=lu.getX();
                            //平行四边形两个未知坐标点计算方法:(x1-(x3-x1),y3),(x1+(x3-x1),y1)
                            var x2=x3+(x3-x1);
                            var x4=x1-(x3-x1);
                            var ld=setCuPointXY(x2,lu.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象
                            var ru=setCuPointXY(x4,p.getY(),2);//设置共享的临时坐标点,用于防止重复创建对象
                            var poly=getCuGraph();//获取当前图形
                            poly.set([lu,ru,p,ld]);
                            poly.draw();//即时绘制
                            break;
                        case graphkind.trapezoid://梯形
                            var lu=getStartPoint();
                            var x3=p.getX();
                            var x1=lu.getX();
                            //梯形两个未知坐标点计算方法:(x3-(x3-x1)/2,y1),(x1-(x3-x1)/2,y3)
                            var x2=x3-(x3-x1)/2;
                            var x4=x1-(x3-x1)/2;
                            var ld=setCuPointXY(x2,lu.getY(),1);
                            var ru=setCuPointXY(x4,p.getY(),2);
                            var poly=getCuGraph();
                            poly.set([lu,ru,p,ld]);
                            poly.draw();
                            break;
                        case graphkind.rect://矩形
                            var lu=getStartPoint();
                            //矩形右上角和左上角坐标计算方法
                            var ld=setCuPointXY(lu.getX(),p.getY(),1);
                            var ru=setCuPointXY(p.getX(),lu.getY(),2);
                            var poly=getCuGraph();
                            poly.set([lu,ru,p,ld]);
                            poly.draw();
                            break;
                        case graphkind.circle://圆
                            var circle=getCuGraph();//获取当前图形
                            circle.set({'start':getStartPoint(),'end':p});
                            circle.draw();//即时绘制
                            break;
                    }
                }
            }
            //鼠标按键松开
            var mouseUp = function(e){
                if(isDrawing()){
                    //console.log("松开鼠标按键:"+e.offsetX+","+e.offsetY);
                    //画笔不需要重绘
                    if(ctrlConfig.kind>1){
                        repaint();
                        getCuGraph().draw();
                    }
                    if(ctrlConfig.kind!=graphkind.poly){//多边形绘制鼠标按键松开不结束绘制,多边形只有右键点击才能结束绘制
                        stopDrawing();//结束绘制
                    }
                }
            }

            //鼠标移出
            var mouseOut = function(e){
                console.log("鼠标移出绘制区域"+e.offsetX+","+e.offsetY);
                if(isDrawing()){
                    console.log("停止绘制");
                    if(ctrlConfig.kind>1){
                        repaint();
                        getCuGraph().draw();
                    }
                    stopDrawing();//停止绘制
                }
            }

            return{
                isNull:isNull,
                getDom:getDom,
                clear:function(){
                    stopDrawing();//停止绘制
                    repaint();
                },
                /**初始化*/
                init:function(params){
                    cbtCanvas=getDom(params.id);
                    //浏览器是否支持Canvas
                    if (cbtCanvas.getContext){
                        /**绘图对象*/
                        cxt=cbtCanvas.getContext("2d");
                        cbtCanvas.onmousedown = mouseDown;
                        cbtCanvas.onmouseup = mouseUp;
                        cbtCanvas.onmousemove = mouseMove;
                        cbtCanvas.onmouseout = mouseOut;
                        resetStyle();//载入样式
                        return true;
                    }else{
                        return false;
                    }
                },
                /**设置背景图片*/
                setBgPic:loadPicture,
                /**选择图形类型*/
                begin:function(k){
                    console.log("选择绘制图形:"+k);
                    if(isNaN(k)){//如果不是数字,先转换为对应字符
                        ctrlConfig.kind=kind[k];
                    }else{
                        ctrlConfig.kind=k;
                    }
                    switchCorser(true);//切换鼠标样式
                },
                /*手型,并停止绘图*/
                hand:function(){
                    ctrlConfig.kind=0;
                    stopDrawing();//停止绘制
                    switchCorser(false);//切换鼠标样式
                }
            }
        })

三、使用方式

1、图形类型

0:鼠标,1:画笔,2:线条,3:三角形,4:矩形,5:多边形,6:圆形,21:箭头,41:平行四边形,42:梯形

var graphkind={'cursor':0,'pen':1,'line':2,'trian':3,'rect':4,'poly':5,'circle':6,'arrow':21,'parallel':41,'trapezoid':42};

2、初始化以及使用背景图片和画笔选择

var drawUtil=new DrawingTools();

//初始化,(如果浏览器不支持H5,会初始化失败,返回false)

if(drawUtil.init({'id':'calibrationCanvas'})){

//加载图片
var imgsrc='图片地址';
if(!drawUtil.isNull(imgsrc)){
    drawUtil.setBgPic(imgsrc,true);//设置背景图片(异步加载图片)
}

}

drawUtil.begin(1);//选择画笔

2、绘制箭头

drawUtil.begin(21);


以上所述就是小编给大家介绍的《原生js实现Canvas实现拖拽式绘图,支持画笔、线条、箭头、三角形和圆形等等图形绘制功能》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Linux Command Line

The Linux Command Line

William E. Shotts Jr. / No Starch Press, Incorporated / 2012-1-17 / USD 39.95

You've experienced the shiny, point-and-click surface of your Linux computer-now dive below and explore its depths with the power of the command line. The Linux Command Line takes you from your very ......一起来看看 《The Linux Command Line》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码