AGG入門

AGG入門

 

一、配置開發環境

 

AGG入門(一) - 配置開發環境

AGG是一個高效的、高質量的、開源的矢量圖形庫,類似的有:GTK+CairoMicrosoftGDI+。在三者中,AGG的性能是最高的(不討論SkiaDirect2D,他們有OGLDX的硬件加速,繪圖速度根本不是一個檔次的)。讓我們細數一下他的優缺點:

  • 優點:效率高,質量高(有反鋸齒),功能強大,跨平臺和平臺GUI支持,……
  • 缺點:沒有硬件加速,文檔少,門檻有那麼點高,……

 

一、準備

 

二、工作

  1. VC6裏新建一個工程“AGG”,類型爲靜態庫Win32 Static Library
  2. 將下列源文件都加入工程:
    1. ./src/ 目錄下所有cpp文件【核心庫】*
    2. ./src/ctrl/ 目錄下所有cpp文件【控件庫】*
    3. ./src/platform/ 下對應平臺目錄(win32)下的所有cpp文件【平臺支持庫】*
    4. ./font_freetype/ 下所有的cpp文件【Freetype字體支持庫】
    5. ./font_win32_tt/ 下所有的cpp文件【Truetype字體支持庫】*
    6. ./gpc/ 下所有的cpp文件【Generic Polygon Clipper裁切庫】
  3. 將下列目錄加入Tools(工具)菜單– Options(選項)菜單 – Directory(目錄)選項卡中的Include Files目錄列表中:
    1. ./include/ *
    2. ./font_freetype/
    3. ./font_win32_tt/ *
    4. ./gpc/
  4. 選擇配置爲Win32 Release,編譯
  5. 編譯成功後,Tools(工具)菜單– Options(選項)菜單 – Directory(目錄)選項卡中的Library Files目錄列表中加入【工程所在目錄】\Release\
  6. 沒有成功編譯的童鞋,獎勵已經編譯好的AGG.lib一枚…… AGG.zip

三、測試

  1. 新建一個Win32 Application 空工程,新建一源文件,加入下面的代碼:

#include <agg_pixfmt_rgb.h>
#include <agg_renderer_base.h>
#include <platform/agg_platform_support.h>

class the_application : public agg::platform_support
{
public:
    the_application(agg::pix_format_e format, bool flip_y) : 
        agg::platform_support(format, flip_y),
        pix_fmt(rbuf_window()),
        ren_bas(pix_fmt) //
初始化渲染器
    { }

    virtual void on_draw()
    {
        ren_bas.reset_clipping(true);
        ren_bas.clear(agg::rgba8(204, 204, 204));
    }

private:
    agg::pixfmt_rgb24 pix_fmt;
    agg::renderer_base<agg::pixfmt_rgb24> ren_bas;

};

int agg_main(int argc, char* argv[])
{
    the_application app(agg::pix_format_rgb24, true);
    app.caption("AGG Test");
    
    if(app.init(500, 500, agg::window_resize)) {
        return app.run();
    }
    return -1;
}

  1. 在【工程】菜單 - 【設置】菜單 - 【連接】選項卡 - 【對象/庫模塊】文本框中加入AGG.lib
  2. 編譯運行,不出意外,可以得到下面的結果:

 

二、平臺支持

AGG入門(二) - 平臺支持

一、先看看下面的代碼,並試着編譯下:

#include <platform/agg_platform_support.h>
#include <agg_pixfmt_rgb.h>
#include <agg_renderer_base.h>
#include <stdio.h>

#include <agg_path_storage.h>

class the_application : public agg::platform_support
{
public:
    the_application(agg::pix_format_e format, bool flip_y) : 
        agg::platform_support(format, flip_y),
        pix_fmt(rbuf_window()),
        ren_bas(pix_fmt) //
初始化渲染器
    {
    }

    virtual void on_draw()
    {
        ren_bas.reset_clipping(true);
        ren_bas.clear(agg::rgba8(255, 255, 255));
    }

    virtual void on_mouse_button_down(int x, int y, unsigned flags)
    {
        if(flags == agg::mouse_left) {
            char str[50];
            sprintf(str, "Mouse location:(%d, %d)", x, y);
            message(str);
        }
    }

    virtual void on_key(int x, int y, unsigned key, unsigned flags)
    {
        if(key == agg::key_return && flags == agg::kbd_shift) {
            unsigned img = 0, states;
            states = create_img(0, 500, 500);
            states = load_img(img, "Steve-and-Bill.bmp");
            copy_img_to_window(img);
            update_window();
        }
    }

private:
    agg::pixfmt_rgb24 pix_fmt;
    agg::renderer_base<agg::pixfmt_rgb24> ren_bas;

};

int agg_main(int argc, char* argv[])
{
    the_application app(agg::pix_format_rgb24, true);
    app.caption("AGG Test");
    
    if(app.init(500, 500, agg::window_resize)) {
        return app.run();
    }
    return -1;
}

如果不出意外,在窗口中點擊鼠標左鍵將會出現對話框提示當前鼠標的位置,而按下Shift+Enter將會在窗口中顯示在工作目錄下的位圖“Steve-and-Bill.bmp”

二、解釋

先看看頭文件:platform/agg_platform_support.h,它裏邊定義了一個platform_support——它允許你建立一個窗口來測試你的圖形,並用鼠標鍵盤去控制它。

類型

  • class platform_support 

主要成員函數

  • platform_support(pix_format_e, bool) : 構造函數。設置窗口風格和y軸是否上下翻轉;
  • width() :返回窗口的寬;
  • height() :返回窗口的高;
  • caption([ const char* ]) :設置標題或返回標題字符串;
  • format() :返回窗口風格;
  • message(const char*) :彈出對話框(沒有風格可選);
  • run() :運行窗口;
  • force_redraw() :重繪窗口,調用on_draw()
  • update_window() :更新窗口,既是把渲染緩存中已有的內容寫入窗口,不調用on_draw()
  • platform_support爲我們提供了一個很好地繪圖平臺,它有一系列的函數可用於操作位圖(BMPPPM):
    • create_img():創建一個編號爲idx的位圖;
    • save_img():將位圖idx保存到文件中;
    • load_img():從文件中加載位圖到idx中;
    • copy_img_to_window():把idx拷貝到渲染緩存裏;
    • copy_img_to_img():把idx拷貝到另一幅位圖裏;
    • copy_window_to_img() :將渲染緩存裏的內容拷貝到位圖。

虛函數(一般都是些消息,要覆蓋它以讓消息循環調用)

  • on_init() :窗口初始化時調用;
  • on_resize(int, int) :改變大小時調用;
  • on_idle()  :空閒時調用;
  • on_mouse_move(int, int, unsigned) :鼠標移動時調用;
  • on_mouse_button_down(int, int, unsigned) :鼠標按下時調用;
  • on_mouse_button_up(int, int, unsigned)  :鼠標彈起時調用;
  • on_key(int, int, unsigned, unsigned)  :鍵盤打字時調用;
  • on_draw()  :窗口重繪時調用。

三、結語

platform_support 的功能不僅僅是這麼多,除此之外,他還能使用控件,等等。但很多時候,成熟的應用是不會使用它的,因爲它封裝了太多,雖然保證了跨平臺性,卻缺乏了自由性。platform_support 的主要作用是測試圖像和修改圖像,方便工作和移植……還有,方便初學者入門……

 

三、渲染器介紹

AGG入門(三) - 渲染器介紹

一、看回AGG入門(二)時on_draw()虛函數裏的代碼:

        agg::rendering_buffer &rbuf = rbuf_window();
        agg::pixfmt_rgb24 pixf(rbuf);

        agg::renderer_base<agg::pixfmt_rgb24> renb(pixf);
        renb.clear(agg::rgba8(255, 255, 255));

        pixf.copy_pixel(20, 20, agg::rgba8(0, 0, 255));

二、渲染器

什麼是渲染?

渲染是把內存中的繪圖指令真正執行的過程。比如說,繪製一條線段,在內存裏只會保存着兩個端點的座標和線段的寬度,而渲染就把這兩個端點轉換爲位圖、緩存甚至顯示屏上的一個個像素的數據。又比如說,紙飛機下面肯定是要有投影的了,但這個投影的質量,就由渲染器決定;線段是走樣的(A),還是反走樣的(B),靠的就是渲染器的指令了。

AGG裏的渲染器

AGG分有多種渲染器。在AGG中,渲染器負責表現掃描線中的每個線段。在渲染器之前,AGG圖形中的線段是沒有顏色值的,只是位置、長度和覆蓋率(透明度)。渲染器賦於線段色彩,最終成爲一幅完整的圖像。其中最常用的是:

  • 像素格式渲染器
  • 基礎渲染器
  • 掃描線(反鋸齒)渲染器

三、三種渲染器間的關係

  • 像素格式渲染器(PixelFormat Renderer)是最基礎的渲染器,不需要任何其他渲染器的支持,所以可以直接聲明;
  • 基礎渲染器(Base Renderer)是中級渲染器,需要像素格式作爲模版和像素格式渲染器的支持;
  • 掃描線渲染器(Scanline Renderer)是高級渲染器,需要基礎渲染器作爲模版和支持;

(注:模版主要是爲了獲取像素格式的信息)

所以,除像素格式渲染器聲明爲:
agg::class object(agg::rendering_buffer &);
之外其他的渲染器都聲明爲:

agg::class<template> object(template &);

 

 

 

四、渲染緩存和混合器

AGG入門(四) - 渲染緩存和混合器

一、上一節的代碼

        agg::rendering_buffer &rbuf = rbuf_window();
        agg::pixfmt_rgb24 pixf(rbuf);

        agg::renderer_base<agg::pixfmt_rgb24> renb(pixf);
        renb.clear(agg::rgba8(255, 255, 255));

        pixf.copy_pixel(20, 20, agg::rgba8(0, 0, 255));

二、渲染緩存

渲染緩存保存着一個個像素,作爲AGG的畫布。它僅僅是一個內存塊,用來儲存像素信息,不提供任何繪圖功能,只允許你讀取和修改裏面的數據。它也不告訴你裏面的像素是灰度的、RGB的還是RGBA的,不告訴你從哪裏到哪裏是一個像素——它只是用來管理內存數據的。

頭文件

#include "platform/agg_platform_support.h"

類型定義

typedef row_accessor<int8u> rendering_buffer //int8u8 bit無符號整形

基本成員函數

  • rendering_buffer(int8u* buf, unsigned width, unsigned height, int stride) 
            
    構造函數,指定事先分配好的內存塊(到時就畫到上面)首地址、寬高、一行的字節數(默認全部都是0);
  • row_ptr(int y)
            返回第y行的首地址;
  • copy_from(void *buf)
            
    buf中拷貝像素;
  • clear(int8u value)
            
    value清空緩存
  • buf(), height(), weight(), stride()
            返回緩存首地址、寬高、一行的字節數;

注:代碼中的rbuf_window()platform_support的一個成員函數,用於返回platform_support一開始幫你申請的緩存引用。

三、混合器

混合器的存在是爲了適應不同平臺、不同需求下的不同像素格式。混合器有三種:agg::rgbaagg::rgba8agg::rgba16,都是用來指定顏色的,rgba每個通道儲存爲doublergba8unsigned charrgba16intlong int;混合器起到的作用就像Win32API裏的RGBCOLORREF宏。

頭文件

#include "agg_pixfmt_rgba.h"

類型定義

struct rgba8; //對,你沒有看錯,是結構,不是類……

基本成員函數

  • rgba8(unsigned r, unsigned g, unsigned b, unsigned a)
            
    無須解釋了吧,最大255
  • clear(), no_color()
            四個通道全部清零,也就是變沒色咯;
  • transparent()
            alpha
    清零,變透明;
  • opacity()
            返回透明度,用double表示;
  • gradient(agg::rgba8 &c, double k)
            顏色梯度,就是顏色變爲從原先的顏色漸變爲c,變化率爲k
  • add(agg::rgba8 &c, unsinged cover)
            
    顏色疊加,疊加一個透明度爲cover/255的顏色c

成員變量

  • r, g, b, a都是無符號整型;

四、像素格式混合器


像素格式混合器的作用是直接操作像素(也就是緩存裏保存的數據,但起碼有個像素的樣子),起到Win32API裏的SetPixel()GetPixel()的作用。像素格式由兩個屬性決定:混合器類型agg::rgba8/agg::rgba16bgr/rgb/rgba/abgr順序agg::order_bgr/agg::order_rgb/agg::order_rgba/agg::order_abgr——這樣,共8種像素格式,它們起名字的規則就是:

agg::pixfmt_[order][bits*3];

下面用最常用的agg::pixfmt_rgb24來解釋:

頭文件

#include "agg_pixfmt_rgb.h"

類型定義

typedef pixfmt_alpha_blend_rgb<blender_rgb<rgba8,  order_rgb>, rendering_buffer> pixfmt_rgb24;

基本成員函數

  • pixfmt_rgb24(agg::rendering_buffer &) 
            構造函數,指定緩存就好;
  • blend_pixel(agg::rgba8& c, int x, int y, int8u cover)
            
    用顏色ccover(覆蓋率=透明度)的透明度混合像素(x, y)
  • copy_pixel(agg::rgba8& c, int x, int y)pixel(int x, int y)
           
    這個就是相當於SetPixel()GetPixel()了;
  • copy_hline(int x, int y, unsigned len, agg::rgba8& c)
    copy_vline(int x, int y, unsigned len, agg::rgba8& c)  
            
    (x, y)開始打橫(豎)順序設置len長度的像素;
  • blend_hline(int x, int y unsigned len, agg::rgba8& c, int8u cover)
    blend_vline
    (int x, int y unsigned len, agg::rgba8& c, int8u cover) 
            
    (x, y)開始打橫(豎)順序混合len長度的像素;
  • copy_solid_hspan(int x, int y unsigned len, agg::rgba8* colors)
    copy_solid_vspan(int x, int y
    unsigned len, agg::rgba8* colors)
    blend_solid_hspan
    (int x, int y unsigned len, agg::rgba8* colors, int8u* cover, int8u cover)
    blend_solid_vspan
    (int x, int y unsigned len, agg::rgba8* colors, int8u* cover, int8u cover)
            同上兩個,不過不是一個顏色,是一系列的顏色;
  • for_each_pixel(void (*f)(agg::rgba8* color))
            每一像素執行一遍f
  • copy_from(agg::rendering_buffer & from, int xdst, int ydst, int xsrc, int ysrc, unsigned len)
    blend_from
    (agg::rendering_buffer & from, int xdst, int ydst, int xsrc, int ysrc, unsigned len[, unsigned cover])  
            
    從緩存form(xsrc, ysrc)順序複製(混合)到當前緩存的(xdst, ydst)中;

【其他函數和像素格式就要靠大家的舉一反三,觸類旁通了……

五、結語

上面說的三者關係是:混合器混合RGBA四個通道,像素格式混合器混合像素,像素格式混合器操作的結果是使渲染緩存裏的數據發生變化,而混合器則不會,因爲它的作用僅僅是表示顏色。

 

 

五、基礎渲染器

AGG入門(五) - 基礎渲染器

基礎渲染器(Base Renderer)是掃描線渲染器的基礎,可以說,正常情況下,你繪畫任何圖形、做任何事,都需要通過它。而基礎渲染器需要你以模版的形式提供像素格式的信息,他將會通過像素格式混合器來實現渲染。其實,基礎渲染器比像素格式混合器多了剪裁盒的功能,其他混合、拷貝什麼的和像素格式混合器是相似的,這裏就不列出來了。

矩形類

AGG封裝了一個專門表示矩形的模板類rect_base,方便矩形的操作。下面用rect_i說明一下。        

頭文件

#include "agg_basics.h"

類型定義

typedef rect_base<int> rect_i;

基本成員函數

  • rect_i(x1, y1, x2, y2)
            構造函數,給出最小和最大座標;
  • normalize()
            修正x1>x2y1>y2的不合法矩形;
  • clip(rect_i& r)
            取當前矩形與r相交的區域矩形作爲當前矩形;
  • is_valid()
            檢查矩形是否合法;
  • hit_test(int x, int y)
            檢查(x, y)是否在矩形內;

基礎渲染器

頭文件

#include "agg_renderer_base"

類型定義

template<class PixelFormat> class renderer_base

基本成員函數

  • renderer_base(PixelFormat)
            構造函數,提供像素格式;
  • ren()
            返回像素格式對象;
  • clip_box(x1,y1,x2,y2)
    clip_box_naked(x1,y1,x2,y2)

            設置當前剪裁盒爲x1,y1,x2,y2圍成的剪裁盒,前者檢查剪裁盒是否合法,後者不檢查。
  • reset_clipping(bool visibility)
            重置剪裁盒,visibility決定剪裁盒是鋪滿窗口(可視)還是0(不可視);
  • clip_box()
    xmin()

    ymin()
    xmax()
    ymax()

            返回當前剪裁盒矩形、以及縱橫座標;
  • copy_from()
    blend_from()

            可以比較方便地、以矩形方式拷貝和混合緩存裏的圖像了;

 

六、練習和細節

AGG入門(六) - 練習和細節

學到目前爲止,已經認識了六個類型:

  • platform_support
  • rendering_buffer
  • rgba8
  • pixfmt_rgb24
  • rect_i
  • renderer_base

現在來做些練習,看看有沒有掌握學過的東西,並且靈活運用吧。

一、基本框架

這一節的程序都以這個框架爲基礎,都是在on_draw中稍微改動的:

#include <agg_pixfmt_rgb.h>
#include <agg_renderer_base.h>
#include <platform/agg_platform_support.h>

class the_application : public agg::platform_support
{
public:
    the_application(agg::pix_format_e format, bool flip_y) : 
        agg::platform_support(format, flip_y),
        pix_fmt(rbuf_window()),
        ren_bas(pix_fmt) //
初始化渲染器
    { }
    
    virtual void on_draw()
    {
         ren_bas.reset_clipping(true);

         ren_bas.clear(agg::rgba8(255, 255, 255));

    }
private:
    agg::pixfmt_rgb24 pix_fmt;
    agg::renderer_base<agg::pixfmt_rgb24> ren_bas;

};

int agg_main(int argc, char* argv[])
{
    the_application app(agg::pix_format_bgr24, true);
    app.caption("AGG Test");
    
    if(app.init(500, 500, agg::window_resize)) {
        return app.run();
    }
    return -1;
}

二、畫線函數

編寫如下函數,實現在渲染緩存中畫線的功能(無需反鋸齒):
    inline void stroke_line(int x1, int y1, int x2, int y2, agg::rgba8& color);
參數:

  • x1, y1, x2, y2分別是兩個端點的座標;
  • color是顏色;

 

三、畫圓函數

編寫如下函數,實現在渲染緩存中畫圓的功能(無需反鋸齒):
    void stroke_round(int r, int C_x, int C_y, agg::rgba8& color, float step = 0.01)
參數:

  • C_x, C_y 是圓心的座標;
  • color是顏色;
  • step是步長,也就是吧圓細分成1/step邊形;

 

四、答案

  • 畫線函數

inline void stroke_line(int x1, int y1, int x2, int y2, agg::rgba8& color)
{
    double precision = max(abs(x1 - x2), abs(y1 - y2));
    //
精度,也就是畫多少個點

    for(int i=0; i <= precision; i++)
        ren_bas.copy_pixel( x1 + ( x2 - x1 ) / precision * i, 
//x
            y1 + ( y2 - y1 ) / precision * i, 
//y
            color);
}

  • 畫圓函數

void stroke_round(int r, int C_x, int C_y, agg::rgba8& color, float step = 0.01)
{
    int prev_x = int(r * cos(-0.01)) + C_x,
        prev_y = int(r * sin(-0.01)) + C_y; //
保存上一個點

    int x, y; //保存當前的點
    for(double rad = 0; rad < 2 * PI + step; rad+= step) {
        x = int(r * cos(rad)) + C_x;
        y = int(r * sin(rad)) + C_y; //
計算弧度爲rad時的座標
        stroke_line(x, y, prev_x, prev_y, color);
        prev_x = x; prev_y = y;
    }
}

可能有的人會覺得奇怪的是,爲什麼在畫線函數中,不用pix_fmt.copy_pixel()而用ren_bas.copy_pixel()呢?因爲,在pix_fmt中,混合器不進行檢查,像素拷貝的時候會拷貝到剪裁區域以外,這樣會造成很奇怪的情況,以至於如果寫到了緩存以外,還會出現異常。注意,剪裁盒功能是基礎渲染器級別才提供的,更加底層的操作,比如像素格式混合和直接操作緩存,高層次的渲染器是無從管理的。爲了安全起見,建議少碰基礎渲染器以下的工具……

 

 

七、頂點源

AGG入門(七) - 頂點源

一、修改模板

現在終於進入了真正的矢量繪圖階段,我們的模版也需要有所改變;至於爲什麼,有什麼作用,以後會說到;
包含下面的頭文件,並且在the_application類中添加兩個成員。

//掃描線和掃描線光柵器
#include <agg_scanline_u.h>
#include <agg_rasterizer_scanline_aa.h>

private:
    //
掃描線和掃描線光柵器
    agg::scanline32_u8 scanline;
    agg::rasterizer_scanline_aa<> rasterizer;

 

 二、頂點源

 

頂點源(Vertex Source)不是一個類,而是一種類的模式。這種類裏面有rewind()函數和vertex()函數給AGG內部調用(沒錯,這就是它的定義)。類如其名,頂點源就是爲繪圖系統提供頂點信息的,大家能想象得出這兩個函數的作用了嗎?

rewind():回到最開始個步驟;

vertex(double* x, double* y):每調用一次,跳一個步驟(點),每一個步驟都輸出頂點的x,y座標(灰色字),以及這個座標的繪圖命令(紫色字);

三、內置頂點源

AGG內置了大量的頂點源,我們可以直接調用,他們包括:

agg::path_storage

agg::arc

agg::rounded_rect

agg::ellipse

agg::curve3

agg::curve4 ......

等等,爲什麼沒有線、點頂點源?其實,path_storage已經內置了畫線函數、畫弧函數、畫貝塞爾曲線函數,你可以用path_storage創造幾乎任何的圖形。至於畫點,copy_pixel()或者用橢圓吧……

四、路徑儲存器

Path storage 是用來管理路徑、畫複雜圖形的。在上面可以任意添加直線、曲線、其他路徑。

頭文件

#include <agg_path_storage.h>

類型定義

typedef path_base<vertex_block_storage<double> > path_storage;

基本成員函數

  • move_to()
        添加命令爲 path_cmd_move_to 的頂點,意爲下一條線從這個點開始畫;
  • line_to()
        添加命令爲 path_cmd_line_to 的頂點,意爲畫線到這個點;
  • arc_to(double rx, double ry, double angle, bool large_arc_flag, bool sweep_flag, double x, double y)
        添加一條弧路徑,畫軸長爲rx, ry,角度爲angle,優/劣弧,順逆時針,終點在(x,y)
  • curve3_to()
        添加貝塞爾曲線,參數爲一個控制點和終點的座標
  • curve4_to()
        添加貝塞爾曲線,參數爲兩個控制點和終點的座標
  • join_path()
        添加一個頂點源,即組合
  • vertex(unsigned idx, double* x, double* y)
    last_vertex(double* x, double* y)
    vertex(double* x, double* y)

        取頂點位置,前者爲已知步驟,後兩者爲順序或倒序獲取
  • modify_vertex()
    modify_command()

        修改步驟爲idx的頂點座標和命令

五、其他頂點源

其他頂點源就不一一介紹了,只列出其頭文件和構造函數:

  • #include <agg_ellipse.h>
    ellipse(double x, double y, double rx, double ry, unsigned num_steps=0, bool cw=false)
        
    圓心(x, y)和長短半軸分別爲rx, ry,步驟數位num_steps(無用),cw決定相交地方是否空出
  • #include <agg_arc.h>
    arc(double x,  double y, double rx, double ry, double a1, double a2, bool ccw=true)
        
    圓心爲(x, y)和長短半軸分別爲rx, ry,初始角度和終結角度爲a1, a2
  • #include <agg_curves.h>
    curve3(double x1, double y1, double x2, double y2, double x3, double y3)
        
    三個點,分別爲:初始點,控制點一,終結點
  • #include <agg_curves.h>
    curve4(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
        
    四個點,分別爲:初始點,控制點一,控制點二,終結點
  • #include <agg_rounded_rect.h>
    rounded_rect(double x1, double y1, double x2, double r)
        
    對角點的座標和圓角半徑

至於怎樣把他們畫在渲染內存上呢,我們下一篇會講到。
頭文件也漸漸地多了起來,包含頭文件時的工作量有點大;我特地列了一個頭文件,裏面已經包含了所有的AGG頭文件,以後大家只需要包含它就好了。
下載處:http://www.cppblog.com/Files/Shihira/agg.h.zip

發佈了20 篇原創文章 · 獲贊 13 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章