用 C++ 和 libgd 来绘图

用 C++ 和 libgd 来绘图

[TOC]

旧文转贴, 代码很久以前写的, 大约至今有十年了, 最近看到有人问如何用 C++ 来生成图表.
有一个 graphviz 的开源库可以用, 它用了自己的领域特定语言 DSL: dot 来生成图表, 具体应用可见 http://graphviz.org/

当然也可以不用这么重的开源库, 这里介绍了以前写的一个chart 库, 几百行代码, 采用了比较原始的作法, 调用了 libgd 基础API, 如画点, 画线等原子方法来绘制图表, 可以应用于一些比较简单的场合

实例

先看看生成的图表实例

折线图和鱼骨头图

1598924-3222d7a01232cd16
这里写图片描述

圆饼图和直方图

1598924-a3aa6fdbb00349b7
这里写图片描述

示例代码

上面两个图表由以下几十行代码来实现

#include "TinyUtil.h"
#include "TinyChart.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory>


int main(int argc, char **argv)
{
    printf("-- write image by gd ---");
    {
        STR_VEC xScales;
        xScales.reserve(10);
        xScales.push_back("11-09");
        xScales.push_back("11-10");
        xScales.push_back("11-11");
        xScales.push_back("11-12");
        xScales.push_back("11-13");
        xScales.push_back("11-14");
        xScales.push_back("11-15");
        xScales.push_back("11-16");
        xScales.push_back("11-17");
        xScales.push_back("11-18");
        xScales.push_back("11-19");
        xScales.push_back("11-20");
        xScales.push_back("11-21");
        xScales.push_back("11-22");

        INT_VEC yScales;
        yScales.reserve(10);
        yScales.push_back(200);
        yScales.push_back(130);
        yScales.push_back(3456);
        yScales.push_back(2345);
        yScales.push_back(1320);
        yScales.push_back(30);
        yScales.push_back(2200);
        yScales.push_back(1330);
        yScales.push_back(3330);
        yScales.push_back(332);
        yScales.push_back(788);
        yScales.push_back(200);
        yScales.push_back(13890);
        yScales.push_back(200);

        TinyTrendlineDiagram* pt = new TinyTrendlineDiagram(
                "latency_trendline.png", 800, 250);
        pt->SetTitle("Trend of Latency (the middle network latency is the value of 0 coordinate)");
        pt->SetLabels(xScales);
        pt->SetValues(yScales);

        pt->Draw();
        pt->WriteImage();
        delete pt;
    }

    {
        STR_VEC xScales;
        xScales.push_back("America");
        xScales.push_back("China");
        xScales.push_back("Japan");
        xScales.push_back("England");
        xScales.push_back("France");
        xScales.push_back("Germany");
        xScales.push_back("South Korean");
        xScales.push_back("India");

        INT_VEC yScales;
        yScales.push_back(4800);
        yScales.push_back(3008);
        yScales.push_back(100);
        yScales.push_back(20);
        yScales.push_back(2000);
        yScales.push_back(178);
        yScales.push_back(258);
        yScales.push_back(1789);

        TinyDistributionDiagram* pd = new TinyDistributionDiagram(
                "users_distribution.png", 800, 600);
        pd->SetTitle("Daily active users distribution among the countries");
        pd->SetLabels(xScales);
        pd->SetValues(yScales);
        pd->Draw();
        pd->WriteImage();
        delete pd;
    }
    return 0;
}

�设计与实现

%20Cool%20Class%20Diagram,%20%5BSharp%5D%5E%5BArc%5D,%20%5BArc%5D%5E%5BEclipse%5D,%20%5BArc%5D%5E%5BCircle%5D,%20%5BSharp%5D%5E%5BRectangle%5D,%20%5BSharp%5D%5E%5BScale%5D,%20%5BSharp%5D%5E%5BChart%5D,%20%5BChart%5D%5E%5BCurveChart%5D,%20%5BChart%5D%5E%5BColumnChart%5D,%20%5BChart%5D%5E%5BPieChart%5D,%20%5BChart%5D%5E%5BHistogramChart%5D,%20%5BDiagram%5D-%5Bnote:Aggregate%20chart%7Bbg:wheat%7D%5D,%20%5BDiagram%5D%5E%5BDistributionDiagram%5D,%20%5BDiagram%5D%5E%5BTrendlineDiagram%5D,%20%5BTrendlineDiagram%5D%3C%3E-0..*%3E%5BCurveChart%5D,%20%5BTrendlineDiagram%5D%3C%3E-0..*%3E%5BColumnChart%5D,%20%5BDistributionDiagram%5D%3C%3E-0..*%3E%5BPieChart%5D,%20%5BDistributionDiagram%5D%3C%3E-0..*%3E%5BHistogramChart%5D

其实, 也就是封装了libgd 的原子操作, 绘制了基本的图形单元

�1) 首先下载并编译依赖库 libgd

  • 下载: Download libgd-2.2.1.tar.gz
  • 解压: tar xvfz libgd-2.2.1.tar.gz
  • 构建步骤 build steps
    cd libgd-2.2.1
    mkdir bld
    cd bld
    cmake -DENABLE_PNG=1 -DENABLE_JPEG=1 -DENABLE_FREETYPE=1 ..
    make
    make install
  1. 然后实现上图所示的类, 加上测试, 约有千余行代码, 放在 github 里

https://gist.github.com/walterfan/b7200fd3e5315ec1e16551fca096a67e

附以上类图的源码, 由 http://yuml.me 生成

// Cool Class Diagram
[Sharp]^[Arc]
[Arc]^[Eclipse]
[Arc]^[Circle]
[Sharp]^[Rectangle]
[Sharp]^[Scale]
[Sharp]^[Chart]
[Chart]^[CurveChart]
[Chart]^[ColumnChart]
[Chart]^[PieChart]
[Chart]^[HistogramChart]
[Diagram]-[note:Aggregate chart{bg:wheat}]
[Diagram]^[DistributionDiagram]
[Diagram]^[TrendlineDiagram]
[TrendlineDiagram]<>-0..*>[CurveChart]
[TrendlineDiagram]<>-0..*>[ColumnChart]
[DistributionDiagram]<>-0..*>[PieChart]
[DistributionDiagram]<>-0..*>[HistogramChart]

接口如下, 代码比较老, 欢迎指正


#include <iostream>
#include <vector>
#include <string>
#include <map>

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#include "gd.h"
#include "gdfonts.h"
#include "gdfontt.h"

using namespace std;  

#define TinyPoint gdPoint
#define TinyFont gdFont
#define TinyColor int
#define WHITE 255,255,255
#define BLACK 0,0,0
#define GRAY 192,192,192
#define RED 255,0,0
#define GREEN 0,255,0
#define BLUE 0,0,255
#define MAGENTA 255,0,255
#define CYAN 0,255,255
#define YELLOW 255,255,0
#define AZURY 153,153,255
#define ORANGE_RED  255,36,00

#define PI 3.14159265357989

#ifdef NDEBUG
#define TRACE(msg)
#else
#define TRACE(msg)  cout<<__FILE__<<", "<<__LINE__<<": "<<msg<<endl;
#endif

typedef map<string, int, less<string>, allocator<int> > STR2INT_MAP;
typedef vector<int, allocator<int> > INT_VEC;
typedef vector<string, allocator<string> > STR_VEC;

const int NAMESIZE=256;
const int DAYS=14;

class TinyShape
{
public:
    TinyShape(gdImagePtr im)
    {
        m_pImage=im;
        m_nBorderWidth=1;
        SetBgColor(WHITE);
        SetFrColor(BLACK);
        SetBorderColor(BLACK);
        m_nBorderWidth=1;
    };
    void SetBgColor(int r,int g,int b)
    {
        m_BgColor=gdImageColorAllocate(m_pImage, r, g, b);
    };
    void SetFrColor(int r,int g,int b)
    {
        m_FrColor=gdImageColorAllocate(m_pImage, r, g, b);
    };

    void SetBorderColor(int r,int g,int b)
    {
        m_BorderColor=gdImageColorAllocate(m_pImage, r, g, b);
    };
    void SetBgColor(TinyColor color)
    {
        m_BgColor=color;
    };
    TinyColor GetBgColor()
    {
        return m_BgColor;
    };
    TinyColor GetBorderColor()
    {
        return m_BorderColor;
    };
    void SetFrColor(TinyColor color)
    {
        m_FrColor=color;
    };

    void SetBorderColor(TinyColor color)
    {
        m_BorderColor=color;
    };
    void SetBorderWidth(int w)
    {
        m_nBorderWidth=w;
    };
    virtual ~TinyShape()
    {
        m_pImage=NULL;
    };
    virtual void Draw() = 0;

    virtual void Fill() { };
protected:
    int m_nBorderWidth;
    gdImagePtr m_pImage;
    TinyColor m_BorderColor;
    TinyColor m_BgColor;
    TinyColor m_FrColor;
};

class TinyScale:public TinyShape
{
public:
    TinyScale(gdImagePtr im,TinyColor color):TinyShape(im)
    {
        m_FrColor=color;
        m_pFont=gdFontSmall;//gdFontTiny
        m_TopLeftPoint.x=0;
        m_TopLeftPoint.y=0;
        m_OffsetPoint.x=0;
        m_OffsetPoint.y=0;

    };
    void Draw()
    {
        gdImageString(m_pImage, m_pFont, m_TopLeftPoint.x+m_OffsetPoint.x, m_TopLeftPoint.y+m_OffsetPoint.y, (unsigned char*)m_text, m_FrColor);
    };
    void Draw(char * text)
    {
        TRACE(" TinyScale:Draw(char * text): " <<m_TopLeftPoint.x<<", "<<m_TopLeftPoint.y<<","<<text);
        gdImageString(m_pImage, m_pFont, m_TopLeftPoint.x+m_OffsetPoint.x, m_TopLeftPoint.y+m_OffsetPoint.y, (unsigned char*)text, m_FrColor);
    };
    void SetPosition(int x,int y)
    {
        m_TopLeftPoint.x=x;
        m_TopLeftPoint.y=y;
    };
    void SetOffset(int x,int y)
    {
        m_OffsetPoint.x=x;
        m_OffsetPoint.y=y;
    };
    void SetText(const char * text)
    {
        strncpy(m_text,text,10);
    };
    void SetFont(gdFontPtr font)
    {
        m_pFont=font;
    };
    TinyScale(gdImagePtr im,int x,int y):TinyShape(im)
    {
        m_TopLeftPoint.x=x;
        m_TopLeftPoint.y=y;
        SetFrColor(BLACK);
        m_pFont=gdFontSmall;
        m_OffsetPoint.x=0;
        m_OffsetPoint.y=0;
    };
    virtual ~TinyScale()
    {};
public:
    TinyPoint m_TopLeftPoint;
    TinyPoint m_OffsetPoint;
    char m_text[10];
private:
    gdFontPtr m_pFont;
};

class TinyRectangle:public TinyShape
{
public:
    TinyRectangle(gdImagePtr im,int x1,int y1,int x2,int y2):TinyShape(im)
    {
        SetSize(x1,y1,x2,y2);
    };
    TinyRectangle(gdImagePtr im):TinyShape(im)
    {

    };
    virtual ~TinyRectangle(){};

    TinyPoint m_TopLeftPoint;
    TinyPoint m_BottomRightPoint;

    void SetSize(int x1,int y1,int x2,int y2)
    {
        m_TopLeftPoint.x=x1;
        m_TopLeftPoint.y=y1;
        m_BottomRightPoint.x=x2;
        m_BottomRightPoint.y=y2;
    };
    void Draw()
    {
        gdImageRectangle(m_pImage,m_TopLeftPoint.x,m_TopLeftPoint.y,\
                        m_BottomRightPoint.x,m_BottomRightPoint.y,m_BorderColor);
    };
    void Fill()
    {
        //gdImageFilledRectangle(m_pImage,m_TopLeftPoint.x,m_TopLeftPoint.y,\
        //              m_BottomRightPoint.x,m_BottomRightPoint.y,m_BgColor);
        int x=(m_TopLeftPoint.x+m_BottomRightPoint.x)/2;
        int y=(m_TopLeftPoint.y+m_BottomRightPoint.y)/2;
        gdImageFillToBorder(m_pImage,x,y,m_BorderColor,m_BgColor);
    };
};
class TinyArc:public TinyShape
{
public:
    TinyArc(gdImagePtr im,int x,int y):TinyShape(im)
    {
        SetCentre(x,y);
        SetAngel(0,0);
        SetSize(0,0);
    };
    TinyArc(gdImagePtr im):TinyShape(im)
    {
    };
    virtual ~TinyArc()
    {};

    TinyPoint GetCentre() {
        return m_Centre;
    }

    void SetCentre(int x,int y)
    {
        m_Centre.x=x;
        m_Centre.y=y;
    };

    void SetAngel(float start,float end)
    {
        m_nStartAngle=start;
        m_nEndAngle=end;
    };

    void SetSize(int w,int h)
    {
        m_nWidth=w;
        m_nHeight=h;
    };
    void Draw()
    {
        gdImageArc(m_pImage,m_Centre.x,m_Centre.y,m_nWidth,m_nHeight,(int)ceil(m_nStartAngle),(int)ceil(m_nEndAngle),m_BorderColor);
    };
    void Fill()
    {
        gdImageFillToBorder(m_pImage,m_Centre.x,m_Centre.y,m_BorderColor,m_BgColor);
    };
    void Fill(TinyColor bordercolor,TinyColor bgcolor)
    {
        gdImageFillToBorder(m_pImage,m_Centre.x,m_Centre.y,bordercolor,bgcolor);
    };
protected:
    TinyPoint m_Centre;
    int m_nWidth;
    int m_nHeight;
    float m_nStartAngle;
    float m_nEndAngle;
};


class TinyCircle:public TinyArc
{
public:
    TinyCircle(gdImagePtr im,int x,int y,int r):TinyArc(im,x,y)
    {
        SetRadius(r);
        m_nStartAngle=0;
        m_nEndAngle=360;
    };
    TinyCircle(gdImagePtr im):TinyArc(im)
    {
    };
    void SetRadius(int r)
    {
        m_Radius=r;
        m_nWidth=2*r;
        m_nHeight=2*r;
    };
    virtual ~TinyCircle()
    {};
private:
    int m_Radius;
};

class TinyEclipse:public TinyArc
{
public:
    TinyEclipse(gdImagePtr im,int x,int y,int r):TinyArc(im,x,y)
    {
        SetRadius(r);
        m_nStartAngle=0;
        m_nEndAngle=0;
    };
    TinyEclipse(gdImagePtr im):TinyArc(im)
    {
    };
    void SetRadius(int r)
    {
        m_Radius=r;
        m_nWidth=2*r;
        m_nHeight=2*r;
    };

    void Draw()
    {
        float fAngle=m_nEndAngle-m_nStartAngle;
        if(fAngle<1)
            return;
        m_Points[0].x=m_Centre.x;
        m_Points[0].y=m_Centre.y;
        m_Points[1].x=m_Centre.x+(int)ceil(m_Radius*cos(m_nStartAngle*PI/180));
        m_Points[1].y=m_Centre.y-(int)ceil(m_Radius*sin(m_nStartAngle*PI/180));
        m_Points[2].x=m_Centre.x+(int)ceil(m_Radius*cos(m_nEndAngle*PI/180));
        m_Points[2].y=m_Centre.y-(int)ceil(m_Radius*sin(m_nEndAngle*PI/180));

        gdImageLine(m_pImage,m_Points[0].x,m_Points[0].y,m_Points[1].x,m_Points[1].y,m_BorderColor);
        gdImageLine(m_pImage,m_Points[0].x,m_Points[0].y,m_Points[2].x,m_Points[2].y,m_BorderColor);
    };
    void Fill()
    {
        int nAngle=(int)ceil(m_nEndAngle-m_nStartAngle);
        int x,y;
        TRACE("Angle: "<<m_nStartAngle<<"-"<<m_nEndAngle<<"="<<nAngle);
        if(nAngle<1)
            return;
        else if(nAngle<=10)
        {
            x=(m_Points[0].x+(m_Points[1].x+m_Points[2].x)/2)/2;
            y=(m_Points[0].y+(m_Points[1].y+m_Points[2].y)/2)/2;
        }
        else if(nAngle<=180)
        {
            x=(m_Points[1].x+m_Points[2].x)/2;
            y=(m_Points[1].y+m_Points[2].y)/2;
        }
        else
        {
            x=2*m_Centre.x-(m_Points[1].x+m_Points[2].x)/2;
            y=2*m_Centre.y-(m_Points[1].y+m_Points[2].y)/2;
        }
        TRACE("see "<<x<<","<<y);

        //gdImageFillToBorder(m_pImage,x,y,m_BgColor,m_BorderColor);
        gdImageFill(m_pImage,x,y,m_BgColor);
        //gdImageFilledPolygon(m_pImage, m_Points, 3, m_BgColor);

    };
    virtual ~TinyEclipse()
    {};
private:
    int m_Radius;
    TinyPoint m_Points[3];
};

class TinyChart:public TinyShape
{
protected:
    TinyColor m_FrameColor;
    TinyColor m_GridColor;
    TinyColor m_TitleColor;
    char m_szTitle[NAMESIZE];
    int m_nGridWidth;
    int m_nShapeWidth;

    TinyPoint m_TopLeftPoint;
    TinyPoint m_BottomRightPoint;
    TinyPoint m_FrameTopLeftPoint;
    TinyPoint m_FrameBottomRightPoint;

    int m_nLeftMargin;
    int m_nRightMargin;
    int m_nTopMargin;
    int m_nBottomMargin;

    int GetBigerScale(int nScale);

public:
    TinyChart(gdImagePtr im):TinyShape(im)
    {
        SetGridColor(BLACK);
        SetFrameColor(GRAY);
        SetTitleColor(AZURY);
        m_nGridWidth=1;
        m_nShapeWidth=10;
        m_nLeftMargin=0;
        m_nRightMargin=0;
        m_nTopMargin=0;
        m_nBottomMargin=0;
        strncpy(m_szTitle,"",NAMESIZE);

    };
    virtual ~TinyChart()
    {
        for(int i = 0;i<m_Shapes.size();i++)
            delete m_Shapes[i];
        m_Shapes.clear();

        for(int j = 0;j<m_xScale.size();j++)
            delete m_xScale[j];
        m_xScale.clear();

        for(int k = 0;k<m_yScale.size();k++)
            delete m_yScale[k];
        m_yScale.clear();
    };


    void SetPosition(int x1,int y1,int x2,int y2)
    {
        m_TopLeftPoint.x=x1;
        m_TopLeftPoint.y=y1;
        m_BottomRightPoint.x=x2;
        m_BottomRightPoint.y=y2;
    }

    void SetMargin(int top,int bottom,int left,int right)
    {
        m_nLeftMargin=left;
        m_nRightMargin=right;
        m_nTopMargin=top;
        m_nBottomMargin=bottom;
    }
    void SetShapeWidth(int width)
    {
        m_nShapeWidth=width;
    };
    void SetGridColor(int r,int g,int b)
    {
        m_GridColor=gdImageColorAllocate(m_pImage, r, g, b);
    };
    void SetTitleColor(int r,int g,int b)
    {
        m_TitleColor=gdImageColorAllocate(m_pImage, r, g, b);
    };
    void SetFrameColor(int r,int g,int b)
    {
        m_FrameColor=gdImageColorAllocate(m_pImage, r, g, b);
    };

    virtual void SetVerticalScale(INT_VEC& vecYScale){};
    virtual void SetHorziontalScale(STR_VEC& vecXScale);
    //virtual void SetData(){};
    virtual void SetTitle(char* title);

    virtual void Plot(){};
    virtual void DrawTitle();
    virtual void DrawGrid();

    void DrawFrame();
    void DrawBorder();

    void Draw();
protected:
    INT_VEC m_nData;
    vector<TinyShape*> m_Shapes;
    vector<TinyScale*> m_xScale;
    vector<TinyScale*> m_yScale;
    int m_nVerticalScale;
    int m_nHorziontalScale;
};

class TinyCurveChart:public TinyChart
{
public:
    void Plot();
    //void SetData();
    void SetVerticalScale(INT_VEC& vecYScale);
    //void SetHorziontalScale(STR_VEC& vecXScale,int nXScale){};
    TinyCurveChart(gdImagePtr im):TinyChart(im)
    {
        SetFrColor(RED);
    };
    virtual ~TinyCurveChart()
    {};
    void DrawTitle(){};
};

class TinyColumnChart:public TinyChart
{
public:
    void Plot();
    void SetVerticalScale(INT_VEC& vecYScale);
    //void SetHorziontalScale(STR_VEC& vecXScale,int nXScale){};
    TinyColumnChart(gdImagePtr im):TinyChart(im)
    {};
    virtual ~TinyColumnChart()
    {};
};
class TinyHistogram:public TinyChart
{
public:
    void Plot();
    void SetHorziontalScale(STR_VEC& vecXScale);
    void SetVerticalScale(INT_VEC& vecYScale);
    //void SetHorziontalScale(STR_VEC& vecXScale,int nXScale){};
    TinyHistogram(gdImagePtr im):TinyChart(im)
    {};
    virtual ~TinyHistogram()
    {};
};

class TinyPieChart:public TinyChart
{
public:
    void Draw();
    void DrawLegend();
    void DrawFrame();
    void Plot();
    void SetHorziontalScale(STR_VEC& vecXScale);
    void SetVerticalScale(INT_VEC& vecYScale);
    TinyColor GetRandColor(int index=-1);
    TinyPieChart(gdImagePtr im):TinyChart(im), m_nSum(0)
    {

    };
    virtual ~TinyPieChart()
    {};
private:
    int m_nSum;
};
class TinyDiagram
{

public:

    TinyDiagram(const char* filename,int width,int height);
    virtual ~TinyDiagram();

    int SetFileName(const char* szFilename);

    int SetTitle(const char* szTitle);

    virtual void Draw() {};

    void DrawBorder();

    void SetLabels(STR_VEC& xScales);

    void SetValues(INT_VEC& yScales);

    gdImagePtr GetImage()
    {
        return m_pImage;
    };
    void WriteImage();
protected:
    gdImagePtr m_pImage;

    int m_nWidth;
    int m_nHeight;

    char m_szFileName[NAMESIZE];
    char m_szTitle[NAMESIZE];

    INT_VEC m_vecData;
    STR_VEC m_vecName;
};

class TinyTrendlineDiagram:public TinyDiagram
{
public:
    TinyCurveChart* m_pCurveChart;
    TinyColumnChart* m_pColumnChart;

    TinyTrendlineDiagram(const char* filename,int width,int height);
    virtual ~TinyTrendlineDiagram();
    void Draw();
};

class TinyDistributionDiagram:public TinyDiagram
{
public:
    TinyPieChart* m_pPieChart;
    TinyHistogram* m_pHistogram;
    void Draw();
    TinyDistributionDiagram(const char* filename,int width,int height);
    virtual ~TinyDistributionDiagram();

};


#endif /* UTIL_TINYCHART_H_ */

本人最近在学习利用c语言将字符串转成图片的操作,碰巧在网上看到有大神提到可以用这个第三方库,发现功能挺强大的。 官网地址:https://libgd.github.io/ 使用过程中出现的问题:在编译运行测试代码时,提示error while loading shared libraries: libgd.so.3: cannot open shared object file: No such file or directory。 解决方案: 默认情况下,编译器只会使用/lib和/usr/lib这两个目录下的库文件,通常通过源码包进行安装时,如果不指定--prefix,会将库安装在/usr/local/lib目录下;当运行程序需要链接动态库时,提示找不到相关的.so库,会报错。也就是说,/usr/local/lib目录不在系统默认的库搜索目录中,需要将目录加进去。   1、首先打开/etc/ld.so.conf文件   2、加入动态库文件所在的目录:执行vi /etc/ld.so.conf,在"include ld.so.conf.d/*.conf"下方增加"/usr/local/lib"。   3、保存后,在命令行终端执行:/sbin/ldconfig -v;其作用是将文件/etc/ld.so.conf列出的路径下的库文件缓存到/etc/ld.so.cache以供使用,因此当安装完一些库文件,或者修改/etc/ld.so.conf增加了库的新搜索路径,需要运行一下ldconfig,使所有的库文件都被缓存到文件/etc/ld.so.cache中,如果没做,可能会找不到刚安装的库。   经过以上三个步骤,"error while loading shared libraries"的问题通常情况下就可以解决了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值