Chào mừng đến với Diễn đàn lập trình - Cộng đồng lập trình.
Trang 1 của 3 123 CuốiCuối
Kết quả 1 đến 10 của 26
  1. #1
    Ngày tham gia
    Sep 2015
    Bài viết
    0

    Lập trình đồ họa | Từng bước xây dựng cho mình một thư viện đồ họa - GDrawing

    - Mình định sẻ đợi khi nào làm xong GImage rồi mới chuyển sang GDrawing! Nhưng khoảng thời gian này Z hơi bận nên chắc tạm ngưng GImage lại, mình sẻ làm một chút về GDrawing, khi nào Z có thời gian sẻ quay lại hoàn thiện GImage!

    - Vì GImage chưa xong nên mình sẻ làm trên struct của mình vậy, nó chỉ khác ở BYTE và DWORD nên sau này chuyển sang GImage của Z không khó khăn lắm! Các bạn có thể Download struct của mình ở đây :
    http://www.2shared.com/file/3131263/...GoldenSun.html

    - Tất cả code mình post trước đây đều chạy được trên struct này! Nếu bạn nào chạy không được thì cứ hỏi mình!
    - Vì mình dùng VS 9.0 nên nếu bạn nào dùng VC++ 6.0 thì phải tạo lại project mới vậy! Tất cả file đều nằm trong folder G2D, khi tạo project mới , các bạn phải copy folder G2D vào trong project rồi mới, add mấy file đó vào rồi mới compile nha!


    Hy vọng sẻ có nhiều bạn tham gia ^_^!

  2. #2
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    1.SetPixel and GetPixel :
    - Trong GImage cũng có hàm Draw_Pixel, mình chỉ thêm cho đầy đủ thôi!

    Mã:
    void GDrawing_Ind::SetPixel(int x,int y,DWORD Color){    if(x < 0 || x >= m_pGImage->GetWidth() ||        y < 0 || y >= m_pGImage->GetHeight()) return;     m_pGImage->GetBits()[m_pGImage->GetWidth()*y + x] = Color;}
    - Ở đây mình có thể thay đổi tí xíu, thay vì so sánh 4 lần thì sẻ chuyển về 2 lần!

    Mã:
    void GDrawing_Ind::SetPixel(int x,int y,DWORD Color){    if(UINT(x) < m_pGImage->GetWidth() &&        UINT(y) < m_pGImage->GetHeight())    m_pGImage->GetBits()[m_pGImage->GetWidth()*y + x] = Color;}
    - Nếu x < 0 thì UINT(x) sẻ ra một số rất lớn -> ImgWidth, vì vậy mình có thể dùng cách này để giảm số lần so sánh!
    - Tuy nhiên, type cast sẻ làm giản tốc độ nên nhanh hơn không nhiều!

    - GetPixel() cũng giống vậy:

    Mã:
    DWORD GDrawing_Ind::GetPixel(int x,int y){    return (UINT(x) < m_pGImage->GetWidth() &&        UINT(y) < m_pGImage->GetHeight()) ?        m_pGImage->GetBits()[m_pGImage->GetWidth()*y + x] : 0;}
    - Ở đây, nếu x,y ngoài image thì sẻ trả về 0, nếu các bạn muốn chính xác hơn thì có thể truyền biến để nhận giá trị trả về vào trong hàm!
    (Ex: DWORD GDrawing_Ind::GetPixel(int x,int y,DWORD &Color) )

  3. #3
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    À quên, còn phần blending cho pixel nửa chứ!

    Thêm hai hàm này vào:

    Mã:
     struct SColor32{    BYTE Blue,Green,Red,Alpha;}; SColor32 GImage_Ind::MixColors(SColor32 Color1, SColor32 Color2){    DWORD Cl = ((*(DWORD*)&Color1 >> 1) & 0xFF7F7F7F) + ((*(DWORD*)&Color2 >> 1) & 0xFF7F7F7F); // C = C1/2 + C2/2    return *(SColor32*)&Cl;}SColor32 GImage_Ind::AlphaBlend(SColor32 BkColor,SColor32 UpperColor,BYTE Transparency){    BkColor.Blue = ((UpperColor.Blue - BkColor.Blue)*Transparency + (BkColor.Blue << 8)) >> 8;    BkColor.Green = ((UpperColor.Green - BkColor.Green)*Transparency + (BkColor.Green << 8)) >> 8;    BkColor.Red = ((UpperColor.Red - BkColor.Red)*Transparency + (BkColor.Red << 8)) >> 8;    return BkColor;}
    - MixColor() là AlphaBlend() với Transparency = 0.5!

    - Công thức Alpha blend có dạng như sau :
    + Color = UpperColor*Transparency + BkColor*(1 - Transparency) (Transparency <= 1)
    + Hay : Color = ((UpperColor - BkColor)*Transparency + (BkColor << 8) >> 8 (Transparency <= 255)

    - Như vậy blending cho SetPixel sẻ chuyển thành:

    Mã:
    inline void GDrawing_Ind::SetPixel(int x,int y,DWORD Color,BYTE Transparency)    {        if(UINT(x) < m_pGImage->GetWidth() && UINT(y) < m_pGImage->GetHeight())        {            SColor32* pColor = (SColor32*)&m_pGImage->GetBits()[m_pGImage->GetWidth()*y + x];   // A little faster than direct access            SColor32 ColorS = *(SColor32*)&Color;            pColor->Blue = ((ColorS.Blue - pColor->Blue)*Transparency + (pColor->Blue << 8)) >> 8;            pColor->Green = ((ColorS.Green - pColor->Green)*Transparency + (pColor->Green << 8)) >> 8;            pColor->Red = ((ColorS.Red - pColor->Red)*Transparency + (pColor->Red << 8)) >> 8;        }    }
    Hay:

    Mã:
    inline void GDrawing_Ind::SetPixel(int x,int y,DWORD Color,BYTE Transparency)    {        if(UINT(x) < m_pGImage->GetWidth() && UINT(y) < m_pGImage->GetHeight())        {            SColor32* pColor = (SColor32*)&m_pGImage->GetBits()[m_pGImage->GetWidth()*y + x];   // A little faster than direct access            SColor32 ColorS = *(SColor32*)&Color;            *pColor = m_pGImage->AlphaBlend(*pColor,ColorS,Transparency);        }    }
    Tạm thời chỉ có hai phần này thôi, tối về mình sẻ post tiếp phần line()!

  4. #4
    2.Line :
    Bây giờ là tới phần vẻ đường thẳng!
    Mình sẻ dùng thuật toán bresenham để vẻ!

    Theo một số sách về graphics thì như sau!

    Mã:
    void Line(int x1,int y1,int x2,int y2,DWORD Color){    int Dx,Dy,p,Const1,Const2;    int x,y;     Dx = x2 - x1;    Dy = y2 - y1;    p = (Dy << 1) - Dx;    Const1 = Dy << 1;    Const2 = (Dy - Dx) << 1;    x = x1;    y = y1;    SetPixel(x,y,Color);    for(int i = 1;i < x2;++i)    {        if(p < 0) p += Const1;        else        {            p += Const2;            ++y;        }        ++x;        SetPixel(x,y,Color);    }}
    Phần thuật toán bresenham các bạn có thể search trên mạng, rất rõ ràng và đầy đủ! Vì vậy mình không nói nhiều đến nó!

    Nhưng theo code ở trên như vầy thì chưa tốt lắm! Vì chúng ta có thể thấy, khi p >= 0 thì mới tăng y lên mà thôi, còn lại ở mỗi bước lặp đều tăng x lên 1!
    Do đó chúng ta có thể chuyển sang dùng pointer (thay vì dùng SetPixel())!

    Mã:
    void Line(int x1,int y1,int x2,int y2,DWORD Color){    int Dx,Dy,p,Const1,Const2;    int x,y;     Dx = x2 - x1;    Dy = y2 - y1;    p = (Dy << 1) - Dx;    Const1 = Dy << 1;    Const2 = (Dy - Dx) << 1;    x = x1;    y = y1;    DWORD* Ptr = m_pGImage->GetBits();//    SetPixel(x,y,Color);    *Ptr = Color;    for(int i = 1;i < x2;++i)    {        if(p < 0) p += Const1;        else        {            p += Const2;            Ptr += m_pGImage->GetWidth();        //    ++y;        }        ++x;        *Ptr = Color;     //   SetPixel(x,y,Color);    }}
    Phần code ở trên chỉ vẻ được line trong 1/4 của đường tròn mà thôi! Đây là code đầy đủ của hàm Line():

    Mã:
    inline BOOL GDrawing_Ind::Line(int x1,int y1,int x2,int y2,DWORD Color)        {            // Using Bresenham algorithm            // Get two rear points of line which inside drawing area            if(GetValidLine(x1,y1,x2,y2,m_pGImage->GetWidth(),m_pGImage->GetHeight()) <= 0) return 0;            if(y1 > y2)            {                // Be careful with same memory block                x1 = x1^x2;                x2 = x2^x1;                x1 = x1^x2;                        y1 = y1^y2;                y2 = y2^y1;                y1 = y1^y2;            }            UINT Dx = ((x1 - x2 < 0) ? x2 - x1 : x1 - x2) + 1;            UINT Dy = y2 - y1 + 1;             // Vertical line            UINT ImgWidth = m_pGImage->GetWidth();            DWORD* Ptr = &m_pGImage->GetBits()[y1*ImgWidth + x1];            if(x1 == x2)            {                for(UINT i = 0;i < Dy;++i)                {                    *Ptr = Color;                    Ptr += ImgWidth;                }                return 1;            }             // Horizontal line            if(y1 == y2)            {                if(x2 < x1)                {                    x1 = x1^x2;                    x2 = x2^x1;                    x1 = x1^x2;                    Ptr = &m_pGImage->GetBits()[ImgWidth*y1 + x1];                }                for(UINT i = 0;i < Dx;++i) *Ptr++ = Color;                return 2;            }             // Diagonal line            if(Dx == Dy)            {                if(x1 < x2)                {                    for(UINT i = 0;i < Dx;++i)                    {                        *Ptr = Color;                        ++Ptr += ImgWidth;                    }                }                else                {                    for(UINT i = 0;i < Dx;++i)                    {                        *Ptr = Color;                        --Ptr += ImgWidth;                    }                }                return 3;            }             // X - Major line            if(Dx > Dy)            {                // Prepare for drawing                int P = (Dy << 1) - Dx;                int Offset1 = Dy << 1;                int Offset2 = (Dy - Dx) << 1;                if(x1 < x2)                {                    DWORD* pEndPtr = Ptr + Dy*ImgWidth + Dx;                    for(;Ptr < pEndPtr;++Ptr)                    {                        *Ptr = Color;                        if(P < 0) P += Offset1;                        else                        {                            P += Offset2;                            Ptr += ImgWidth;                        }                    }                }                else                {                    DWORD* pEndPtr = Ptr + (Dy - 1)*ImgWidth + x2 - x1;                    for(;Ptr < pEndPtr;--Ptr)                    {                        *Ptr = Color;                        if(P < 0) P += Offset1;                        else                        {                            P += Offset2;                            Ptr += ImgWidth;                        }                    }                    for(;Ptr > pEndPtr;--Ptr) *Ptr = Color;                }            }            else    // Y - Major line            {                // Prepare for drawing                int P = (Dx << 1) - Dy;                int Offset1 = Dx << 1;                int Offset2 = (Dx - Dy) << 1;                int x = x1,y = y1;                if(x1 < x2)                {                    DWORD* pEndPtr = Ptr + (Dy - 1)*ImgWidth + Dx;                    for(;Ptr < pEndPtr;Ptr += ImgWidth)                    {                        *Ptr = Color;                        if(P < 0) P += Offset1;                        else                        {                            P += Offset2;                            Ptr++;                        }                    }                }                else                {                    DWORD* pEndPtr = Ptr + Dy*ImgWidth - Dx;                    for(;Ptr < pEndPtr;Ptr += ImgWidth)                    {                        *Ptr = Color;                        if(P < 0) P += Offset1;                        else                        {                            P += Offset2;                            --Ptr;                        }                    }                }            }            return 4;        }
    Nếu muốn dùng thêm blending thì thay "*Ptr = Color;" bằng hàm blend tương ứng như trong SetPixel()

  5. #5
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    RL này. Cậu chỉnh code sao cho dễ nhìn 1 chút đi (nhất là mấy cái SetPixel ở trên đó). Mình đang hoàn thiện tiếp GImage, thứ 7 mới rãnh.

    GoodLuck.

  6. #6
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Nhìn dễ hơn chưa Z!

  7. #7
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Ok rồi!

    RL ghép SSE và Bresenham được rồi đó. Ta sẽ chia đoạn thẳng thành 4 khúc (nếu đường thẳng dài hơn 40 pixel). SSE có khả năng tính 4 phép tính float tại một thời điểm do đó mình nghĩ tốc độ tăng ít nhất là 3.5 lần trừ đi một ít sai số khi khởi tạo.

  8. #8
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    À, nếu ghép thì mình nghĩ nên ghép chung với MMX! MMX giống như SSE thôi nhưng dùng số nguyên (vì code trên toàn dùng số nguyên)! Z có rảnh thì dùng thử nha!

  9. #9
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    3.Line with antialiasing :
    - Bây giờ là tới phần AA cho line!
    - Mình sẻ dùng thuật toán WU Antialiasing!



    - Việc dùng AA là xác định mỗi row của line có bao nhiêu pixel! Khi đả xác định được số pixel thì chỉ việc lấy 255 chia cho số pixel!
    - Trong hình trên thì row đầu tiên có 8 pixel -> 255/8 ~ 32 -> mỗi lần set một pixel thì Transparency sẻ giảm đi 32!

    - Do đó, theo hình trên thì pixel thứ 1 sẻ có Transparency là 255, pixel thứ 2 sẻ có transparency là (255 - 32)! Việc giảm Transparency sẻ liên tục đến khi tọa độ y của pixel tăng lên 1 (nghĩa là sang row kế) thì Transparency sẻ được reset về 255! Và cứ thế!
    - Nhưng như vậy thì nhìn line sẻ bị hỏng, do đó người ta thêm một line thứ 2 bên dưới hoặc trên line ban đầu (để bù lỗ ^_^)! Kết quả là sẻ có line được AA như hình bên dưới!

    - Các bạn có thể xem về WU Antialiasing trên mạng! Kỹ thuật AA trên là cách mình làm thôi, còn WU thì hơi khác hơn tí xíu! Kỹ thuật AA ở trên có thể áp dụng cho tất cả các line, circle, ellipse, bezier,... nói chung là tất cả các line có chuyển tọa độ y của pixel!

    - Phần code của WU như sau:

    Mã:
    // Get two rear points of line which inside drawing areaif(GetValidLine(x1,y1,x2,y2,m_pGImage->GetWidth(),m_pGImage->GetHeight()) <= 0) return 0; // Prevent continuous-set by using pointerUINT ImgWidth = m_pGImage->GetWidth();UINT ImgHeight = m_pGImage->GetHeight();if(x1 == 0) ++x1;else if(x1 == ImgWidth - 1) --x1;if(x2 == 0) ++x2;else if(x2 == ImgWidth - 1) --x2;if(y1 == 0) ++y1;else if(y1 == ImgHeight - 1) --y1;if(y2 == 0) ++y2;else if(y2 == ImgHeight - 1) --y2; // Reposition P1 on top of P2if(y1 > y2){    // Be careful with same memory block    x1 = x1^x2;    x2 = x2^x1;    x1 = x1^x2;     y1 = y1^y2;    y2 = y2^y1;    y1 = y1^y2;} // Get distance of two pointUINT dx = ((x1 - x2 < 0) ? x2 - x1 : x1 - x2) + 1;UINT dy = y2 - y1 + 1; SColor32 ColorS = *(SColor32*)&Color;SColor32* Ptr = (SColor32*)&m_pGImage->GetBits()[y1*ImgWidth + x1]; BYTE Transparency,PaperTransparency;UINT TRatio = 0;*Ptr = ColorS; UINT Ratio = (dy << 16)/dx;int PrevY2 = y1;SColor32* Ptr2;while(--dx){    TRatio += Ratio;    y2 = y1 + (TRatio >> 16);     ++Ptr;    if(PrevY2 < y2)    {        Ptr += ImgWidth;        PrevY2 = y2;    }    Ptr2 = Ptr + ImgWidth;     Transparency = TRatio >> 8;    PaperTransparency = ~Transparency;     // Original    *Ptr = m_pGImage->AlphaBlend(*Ptr,ColorS,PaperTransparency);     Ptr2 = Ptr + ImgWidth;    *Ptr2 = m_pGImage->AlphaBlend(*Ptr2,ColorS,Transparency);}
    - Phần code trên là cho x - Major line (dx > dy, x1 < x2)
    - Muốn viết các trường hợp còn lại thì chỉ cần đảo các var!

    - Tồi về mình sẻ post tiếp một cách vẻ line và AA mới, nhanh hơn cả WU!

  10. #10
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    3 - 2 Line with antialiasing :
    - Trước khi nói về AA, mình sẻ nói về thuật toán vẻ line mới (tạm gọi là RLight Line vậy ^_^)!

    - Trước tiên xem cái hình này đả :

    - Như trên hình, việc vẻ một line là set từng pixel theo chiều ngang (x,y) (với X Major line)! Đến một lúc nào đó thì tăng y lên 1 (x,y + 1)! Nghĩa là nếu chúng ta biết khi nào cần tăng y lên thì việc vẻ Line xem như xong!
    - Thuật toán bresenham dùng công thức đường thẳng để tính các offset, còn cách của mình là dựa trên ratio giửa dx và dy!

    - Lấy vd, chúng ta sẻ vẻ một line từ 0x0 tới 9x3 -> dy/dx = (3 - 0 + 1)/(9 - 0 + 1) = 0.4
    - Vẻ tay sẻ như sau:
    Khi x = 0 -> y = 1*0.4 = 0.4 -> P(0,0)
    Khi x = 1 -> y = 2*0.4 = 0.8 -> P(1,0)
    Khi x = 2 -> y = 3*0.4 = 1.2 -> P(2,1)
    Khi x = 3 -> y = 4*0.4 = 1.6 -> P(3,1)
    Khi x = 4 -> y = 5*0.4 = 2.0 -> P(4,1)
    Khi x = 5 -> y = 6*0.4 = 2.4 -> P(5,2)
    Khi x = 6 -> y = 7*0.4 = 2.8 -> P(6,2)
    Khi x = 7 -> y = 8*0.4 = 3.2 -> P(7,3)
    Khi x = 8 -> y = 9*0.4 = 3.6 -> P(8,3)
    Khi x = 9 -> y = 10*0.4 = 4.0 -> P(9,3)

    - Kết quả sẻ như hình sau:


    - Nhưng nếu nhân kiểu này thì chết chắc!
    - Vì chúng ta đả biết ratio nên -> biết khi nào sẻ tăng y! Đặc IncWr là var để tăng offset, mỗi lần tăng x, chúng ta sẻ cộng Ratio vào IncWr, nếu IncWr > 1 có nghĩa là y sẻ tăng lên 1! Reset lại IncWr bằng cách trư đi 1!
    - Kết quả sẻ như hình sau:


    - Còn đây là code:

    Mã:
    inline BOOL GDrawing_Ind::LineRl(int x1,int y1,int x2,int y2,DWORD Color){    #pragma region LineRl    // Using RLight Line algorithm    // Get two rear points of line which inside drawing area    if(GetValidLine(x1,y1,x2,y2,m_pGImage->GetWidth(),m_pGImage->GetHeight()) <= 0) return -1;     // Reposition P1 on top of P2    if(y1 > y2)    {        // Be careful with same memory block        x1 = x1^x2;        x2 = x2^x1;        x1 = x1^x2;         y1 = y1^y2;        y2 = y2^y1;        y1 = y1^y2;    }     // Get distance of two point    UINT dx = ((x1 - x2 < 0) ? x2 - x1 : x1 - x2) + 1;    UINT dy = y2 - y1 + 1;     // Prepare for drawing    UINT ImgWidth = m_pGImage->GetWidth();    DWORD* Ptr = &m_pGImage->GetBits()[y1*ImgWidth + x1];     // Vertical line    if(x1 == x2)    {        for(UINT i = 0;i < dy;i++)        {            *Ptr = Color;            Ptr += ImgWidth;        }        return 1;    }    if(y1 == y2)    // Horizontal line    {        if(x2 < x1)        {            x1 = x1^x2;            x2 = x2^x1;            x1 = x1^x2;            Ptr = &m_pGImage->GetBits()[m_pGImage->GetWidth()*y1 + x1];        }        for(UINT i = 0;i < dx;i++) *Ptr++ = Color;        return 2;    }    if(dx == dy)    // Diagonal line    {        if(x1 < x2)        {            for(UINT i = 0;i < dx;i++)            {                *Ptr = Color;                Ptr += ImgWidth + 1;            }        }        else        {            for(UINT i = 0;i < dx;i++)            {                *Ptr = Color;                Ptr += ImgWidth - 1;            }        }        return 3;    }     // X - major line    UINT StepSize = 65536;  // 2^16    if(dx > dy)    {        UINT StepWr = (dy << 16)/dx,IncWr = 0;        if(x1 < x2)        {            DWORD* pEndPtr = Ptr + (dy - 1)*ImgWidth + dx;            for(;Ptr < pEndPtr;++Ptr)            {                IncWr += StepWr;                if(IncWr > StepSize)                {                    IncWr -= StepSize;                    Ptr += ImgWidth;                }                *Ptr = Color;            }        }        else        {            DWORD* pEndPtr = Ptr + (dy - 1)*ImgWidth + x2 - x1;            for(;Ptr < pEndPtr;--Ptr)            {                IncWr += StepWr;                if(IncWr > StepSize)                {                    IncWr -= StepSize;                    Ptr += ImgWidth;                }                *Ptr = Color;            }            for(;Ptr >= pEndPtr;--Ptr) *Ptr = Color;        }    }    else    // Y - major line    {        UINT StepHr = (dx << 16)/dy,IncHr = 0;        if(x1 < x2)        {            DWORD* pEndPtr = Ptr + (dy - 1)*ImgWidth + dx;            for(;Ptr < pEndPtr;Ptr += ImgWidth)            {                IncHr += StepHr;                if(IncHr > StepSize)                {                    IncHr -= StepSize;                    ++Ptr;                }                *Ptr = Color;            }        }        else        {            DWORD* pEndPtr = Ptr + dy*ImgWidth - dx;            for(;Ptr < pEndPtr;Ptr += ImgWidth)            {                IncHr += StepHr;                if(IncHr > StepSize)                {                    IncHr -= StepSize;                    --Ptr;                }                *Ptr = Color;            }        }    }    return 1;    #pragma endregion}
    - Test thử so với bresenham xem sao!

    Line(1,1,400,4) : vẻ 1 000 000 line và cộng dồn trung bình 10 lần!
    + Dùng bresenham : 1.558410 (s)
    + Dùng RLight : 1.274334 (s)

    Line(1,1,4,400) : vẻ 1 000 000 line và cộng dồn trung bình 10 lần!
    + Dùng Bresenham : 5.762071 (s)
    + Dùng RLight : 5.755710 (s)

    Line(1,1,100,4) : vẻ 1 000 000 line và cộng dồn trung bình 10 lần!
    + Bresenham : 0.570991 (s)
    + RLight : 0.515729 (s)

    - Ủa, vậy nhanh hơn bresenham sao ta ???! Test lại với line có độ dài nhỏ xem sao!

    Line(1,1,50,4) : vẻ 1 000 000 line và cộng dồn trung bình 10 lần!
    + Bresenham : 0.367095 (s)
    + RLight : 0.388135 (s)

    Line(1,1,25,4) : vẻ 1 000 000 line và cộng dồn trung bình 10 lần!
    + Bresenham : 0.208329 (s)
    + RLight : 0.243836 (s)

    - Như vậy là vẻ mấy line nhỏ thì thuật toán của mình chậm hơn bresenham, nhưng khi vẻ mấy line dài hơn thì thuật toán của mình lại nhanh hơn (không tin các bạn cứ chép code về test thử)!

    - Có bạn nào biết vì sao vẻ mấy line dài thì bresenham chạy chậm hơn ko ^_^ (đơn giản lắm)?!

 

 
Trang 1 của 3 123 CuốiCuối

Quyền viết bài

  • Bạn Không thể gửi Chủ đề mới
  • Bạn Không thể Gửi trả lời
  • Bạn Không thể Gửi file đính kèm
  • Bạn Không thể Sửa bài viết của mình
  •