📜  2D图形中的剪切(1)

📅  最后修改于: 2023-12-03 14:59:04.645000             🧑  作者: Mango

2D图形中的剪切

简介

在计算机图形学中,剪切是指在二维或三维几何体中提取一个子集的操作。在二维图形中,剪切可以用来裁剪图形的一部分。通常情况下,剪切是通过定义一个裁剪区域来实现的。在裁剪区域之外的部分将被删除或隐藏,而裁剪区域内的部分将被保留。在计算机图形学中,剪切是一种常见的操作,是制作动画和游戏中常用的技术之一。

剪切算法分类

根据剪切算法的原理,我们可以将常见的剪切算法分为以下几类:

线段剪切

线段剪切可以将线段裁剪到裁剪区域内。有几种线段剪切算法,包括框架线剪切、中点线剪切和Cohen–Sutherland剪切算法(也称为直线剪切算法)。Cohen–Sutherland剪切算法是最广泛使用的线段剪切算法之一。它使用二进制数来表示线段所在的区域,并使用位运算来判断线段是否与裁剪区域相交。

多边形剪切

多边形剪切可以将多边形裁剪到裁剪区域内。有几种多边形剪切算法,包括Sutherland–Hodgman多边形剪切算法、Weiler–Atherton多边形剪切算法和Li–Canny多边形剪切算法。其中,Sutherland–Hodgman多边形剪切算法是最广泛使用的多边形剪切算法之一,它通过将多边形的每个顶点和裁剪区域相交来计算多边形的新形状。

图像剪切

图像剪切可以将图像裁剪到裁剪区域内。有几种图像剪切算法,包括绘图API剪切、基于遮罩的剪切和图像数据剪切。绘图API剪切是最简单的剪切算法之一,它可以使用现代图形API的剪切功能来裁剪图像。基于遮罩的剪切是一种更高级的剪切技术,它可以使用遮罩来保留图像的一部分,并在剩余区域中应用其他效果。

算法实施
线段剪切算法 – Cohen–Sutherland

Cohen–Sutherland剪切算法使用二进制数来表示线段所在的区域,并使用位运算来判断线段是否与裁剪区域相交。该算法的实现涉及到一个线段网格分割,分为四个区域:左侧、右侧、上方和下方。每个线段都被分成四个部分,以便在判断重叠区域时更容易处理。如果最终的结果是“1”,则表示该线段与裁剪区域无交集;如果最终结果为“0”,则表示该线段与裁剪区域相交。以下是Cohen–Sutherland线段剪切算法的示例实现:

enum {LEFT=1, RIGHT=2, BOTTOM=4, TOP=8}; // 4边赋值为char的2进制值

int ComputeOutCode(double x, double y, double xMin, double yMin, double xMax, double yMax)
{ //根据点(x,y)相对于窗口的位置关系,返回相应的区域码 
    int code = 0;
    if (x<xMin)
        code |= LEFT;
    else if (x>xMax)
        code |= RIGHT;
    if (y<yMin)
        code |= BOTTOM;
    else if (y>yMax)
        code |= TOP;
    return code;
}

void Cohen_Sutherland_Line_Clip_And_Draw(double x1, double y1, double x2, double y2, double xMin, double yMin, double xMax, double yMax, COLORREF color)
{
    int outCode1 = ComputeOutCode(x1, y1, xMin, yMin, xMax, yMax);
    int outCode2 = ComputeOutCode(x2, y2, xMin, yMin, xMax, yMax);
    bool accept = false;
 
    while (true)
    {
        if (!((outCode1 | outCode2) != 0)) //线段在窗口内部
        {
            accept = true;
            break;
        }
        else if ((outCode1 & outCode2) != 0) //线段在窗口外部
        {
            break;
        }
        else //线段跨越窗口边界
        {
            double x, y;
            int outCode = outCode1 ? outCode1 : outCode2;
            if (outCode & TOP) //线段从顶部离开窗口
            {
                x = x1 + (x2 - x1) * (yMax - y1) / (y2 - y1);
                y = yMax;
            }
            else if (outCode & BOTTOM) //线段从底部离开窗口
            {
                x = x1 + (x2 - x1) * (yMin - y1) / (y2 - y1);
                y = yMin;
            }
            else if (outCode & RIGHT) //线段从右侧离开窗口
            {
                y = y1 + (y2 - y1) * (xMax - x1) / (x2 - x1);
                x = xMax;
            }
            else if (outCode & LEFT) //线段从左侧离开窗口
            {
                y = y1 + (y2 - y1) * (xMin - x1) / (x2 - x1);
                x = xMin;
            }
 
            if (outCode == outCode1) //更新第一个点坐标
            {
                x1 = x;
                y1 = y;
                outCode1 = ComputeOutCode(x1, y1, xMin, yMin, xMax, yMax);
            }
            else //更新第二个点坐标
            {
                x2 = x;
                y2 = y;
                outCode2 = ComputeOutCode(x2, y2, xMin, yMin, xMax, yMax);
            }
        }
    }
 
    if (accept)
    {
        DrawLine(x1, y1, x2, y2, color);
    }
}
多边形剪切算法 – Sutherland–Hodgman

Sutherland–Hodgman多边形剪切算法是最广泛使用的多边形剪切算法之一,它通过将多边形的每个顶点和裁剪区域相交来计算多边形的新形状。该算法的实现涉及到计算多边形的交点,然后根据交点构建新的多边形。以下是Sutherland–Hodgman多边形剪切算法的示例实现:

typedef struct _POINT
{
    double x;
    double y;
} POINT, *PPOINT;

inline double distance(const POINT &p1, const POINT &p2)
{ //求两点距离
    return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}

bool inside(const POINT &p, const POINT &p1, const POINT &p2)
{ //判断点是否在边界内
    double d1 = distance(p, p1);
    double d2 = distance(p, p2);
    double D  = distance(p1, p2);
    return fabs(d1 + d2 - D) <= 0.0001;
}

void sutherland_hodgman_polygon_clip(PPOINT pIn, int nIn, PPOINT pOut, int &nOut, double xMin, double yMin, double xMax, double yMax)
{
    PPOINT pS = pIn + nIn -1;
    for (int j=0; j<4; ++j, --pS) // 扫描全部边界(分别作为裁剪边)、上右下左
    {
        PPOINT pC = pIn + nIn -1;
        int count = 0;
        PPOINT pTemp = pOut;

        for (int i=0; i<nIn; ++i, ++pC)
        {
            if (inside(*pC, *pS, *(pS-1))) // 内部点
            {
                if (!inside(*(pC-1), *pS, *(pS-1))) // 外部点
                {
                    double x = pC->x, y = pC->y;
                    if (pS->x != (pS-1)->x) // 如果非水平边
                    {
                        y = ( pC->y - ( (pS-1)->y ) ) * ( (pS->x)-(pS-1)->x) / ((pS->y)-(pS-1)->y) + (pS-1)->x;
                        if (y > yMax) // 走过裁剪窗口
                            y = yMax; 
                        else if (y < yMin)
                            y = yMin;
                    }
                    else // 如果是水平边
                    {
                        x = pS->x;
                        if (pC->y > (pS-1)->y) // 非自下向上的线段
                        {
                            if (x > xMax)
                                continue;
                            else if (x < xMin)
                                continue;
                        }
                        else if (pC->y < (pS-1)->y) // 自下向上线段
                        {
                            if (x > xMax)
                                x = xMax;
                            else if (x < xMin)
                                x = xMin;
                        }
                    }   
                    pTemp->x = x;
                    pTemp++ ->y = y;
                    count++;
                }
                pTemp->x = pC->x;
                pTemp++ ->y = pC->y;
                count++;
            }
            else if (inside(*(pC-1), *pS, *(pS-1))) // 外部点
            {
                double x = pC->x, y = pC->y;
                if (pS->x != (pS-1)->x) // 如果非水平边
                {
                    y = ( pC->y - ( (pS-1)->y ) ) * ( (pS->x)-(pS-1)->x) / ((pS->y)-(pS-1)->y) + (pS-1)->x;
                    if (y > yMax)
                        y = yMax;
                    else if (y < yMin)
                        y = yMin;
                }
                else // 如果是水平边
                {
                    x = pS->x;
                    if (pC->y > (pS-1)->y) // 非自下向上的线段
                    {
                        if (x > xMax)
                            continue;
                        else if (x < xMin)
                            continue;
                    }
                    else if (pC->y < (pS-1)->y) // 自下向上线段
                    {
                        if (x > xMax)
                            x = xMax;
                        else if (x < xMin)
                            x = xMin;
                    }
                }
                pTemp->x = x;
                pTemp++ ->y = y;
                count++;
            }
        } 
        nIn = count;
        memcpy(pIn, pOut, count * sizeof(POINT));
    }
    nOut = nIn;
}
总结

剪切是计算机图形学中的重要技术之一,可以用于编辑和划分二维图形、三维立体图形、图像等。在实际应用中,剪切算法的选择应根据具体需求来进行。在实现剪切算法时,需要考虑算法的效率以及面对不同情景时的表现。以上是两个常见的剪切算法,我们可以根据实际需要来选择并实现适合自己的算法。