来源:https://blog.csdn.net/u013044116/article/details/49737585
二、扫描线算法(Scan-Line Filling)

扫描线算法适合对矢量图形进行区域填充,只需要直到多边形区域的几何位置,不需要指定种子点,适合计算机自动进行图形处理的场合使用,比如电脑游戏和三维CAD软件的渲染等等。

对矢量多边形区域填充,算法核心还是求交。《计算几何与图形学有关的几种常用算法》一文给出了判断点与多边形关系的算法――扫描交点的奇偶数判断算法,利用此算法可以判断一个点是否在多边形内,也就是是否需要填充,但是实际工程中使用的填充算法都是只使用求交的思想,并不直接使用这种求交算法。究其原因,除了算法效率问题之外,还存在一个光栅图形设备和矢量之间的转换问题。比如某个点位于非常靠近边界的临界位置,用矢量算法判断这个点应该是在多边形内,但是光栅化后,这个点在光栅图形设备上看就有可能是在多边形外边(矢量点没有大小概念,光栅图形设备的点有大小概念),因此,适用于矢量图形的填充算法必须适应光栅图形设备。

2.1扫描线算法的基本思想

扫描线填充算法的基本思想是:用水平扫描线从上到下(或从下到上)扫描由多条首尾相连的线段构成的多边形,每根扫描线与多边形的某些边产生一系列交点。将这些交点按照x坐标排序,将排序后的点两两成对,作为线段的两个端点,以所填的颜色画水平直线。多边形被扫描完毕后,颜色填充也就完成了。扫描线填充算法也可以归纳为以下4个步骤:

(1)       求交,计算扫描线与多边形的交点

(2)       交点排序,对第2步得到的交点按照x值从小到大进行排序;

(3)       颜色填充,对排序后的交点两两组成一个水平线段,以画线段的方式进行颜色填充;

(4)       是否完成多边形扫描?如果是就结束算法,如果不是就改变扫描线,然后转第1步继续处理;

整个算法的关键是第1步,需要用尽量少的计算量求出交点,还要考虑交点是线段端点的特殊情况,最后,交点的步进计算最好是整数,便于光栅设备输出显示。

对于每一条扫描线,如果每次都按照正常的线段求交算法进行计算,则计算量大,而且效率底下,如图(6)所示:

图(6) 多边形与扫描线示意图

观察多边形与扫描线的交点情况,可以得到以下两个特点:

(1)       每次只有相关的几条边可能与扫描线有交点,不必对所有的边进行求交计算;

(2)       相邻的扫描线与同一直线段的交点存在步进关系,这个关系与直线段所在直线的斜率有关;

第一个特点是显而易见的,为了减少计算量,扫描线算法需要维护一张由“活动边”组成的表,称为“活动边表(AET)”。例如扫描线4的“活动边表”由P1P2和P3P4两条边组成,而扫描线7的“活动边表”由P1P2、P6P1、P5P6和P4P5四条边组成。

第二个特点可以进一步证明,假设当前扫描线与多边形的某一条边的交点已经通过直线段求交算法计算出来,得到交点的坐标为(x, y),则下一条扫描线与这条边的交点不需要再求交计算,通过步进关系可以直接得到新交点坐标为(x + △x, y + 1)。前面提到过,步进关系△x是个常量,与直线的斜率有关,下面就来推导这个△x。

假设多边形某条边所在的直线方程是:ax + by + c = 0,扫描线yi和下一条扫描线yi+1与该边的两个交点分别是(xi,yi)和(xi+1,yi+1),则可得到以下两个等式:

axi + byi + c = 0                        (等式 1)

axi+1 + byi+1 + c = 0                     (等式 2)

由等式1可以得到等式3:

xi = -(byi + c) / a                           (等式 3)

同样,由等式2可以得到等式4:

xi+1 = -(byi+1 + c) / a                      (等式 4)

由等式 4 – 等式3可得到

xi+1 – xi = -b (yi+1 - yi) / a

由于扫描线存在yi+1 = yi + 1的关系,将代入上式即可得到:

xi+1 – xi = -b / a

即△x = -b / a,是个常量(直线斜率的倒数)。

“活动边表”是扫描线填充算法的核心,整个算法都是围绕者这张表进行处理的。要完整的定义“活动边表”,需要先定义边的数据结构。每条边都和扫描线有个交点,扫描线填充算法只关注交点的x坐标。每当处理下一条扫描线时,根据△x直接计算出新扫描线与边的交点x坐标,可以避免复杂的求交计算。一条边不会一直待在“活动边表”中,当扫描线与之没有交点时,要将其从“活动边表”中删除,判断是否有交点的依据就是看扫描线y是否大于这条边两个端点的y坐标值,为此,需要记录边的y坐标的最大值。根据以上分析,边的数据结构可以定义如下:

65 typedef struct tagEDGE

66 {

67     double xi;

68     double dx;

69     int ymax;

74 }EDGE;

根据EDGE的定义,扫描线4和扫描线7的“活动边表”就分别如图(7)和图(8)所示:

图(7) 扫描线4的活动边表

图(8) 扫描线7的活动边表

前面提到过,扫描线算法的核心就是围绕“活动边表(AET)”展开的,为了方便活性边表的建立与更新,我们为每一条扫描线建立一个“新边表(NET)”,存放该扫描线第一次出现的边。当算法处理到某条扫描线时,就将这条扫描线的“新边表”中的所有边逐一插入到“活动边表”中。“新边表”通常在算法开始时建立,建立“新边表”的规则就是:如果某条边的较低端点(y坐标较小的那个点)的y坐标与扫描线y相等,则该边就是扫描线y的新边,应该加入扫描线y的“新边表”。上例中各扫描线的“新边表”如下图所示:

图(9) 各扫描线的新边表

讨论完“活动边表(AET)”和“新边表(NET)”,就可以开始算法的具体实现了,但是在进一步详细介绍实现算法之前,还有以下几个关键的细节问题需要明确:

(1)      多边形顶点处理

在对多边形的边进行求交的过程中,在两条边相连的顶点处会出现一些特殊情况,因为此时两条边会和扫描线各求的一个交点,也就是说,在顶点位置会出现两个交点。当出现这种情况的时候,会对填充产生影响,因为填充的过程是成对选择交点的过程,错误的计算交点个数,会造成填充异常。

假设多边形按照顶点P1、P2和P3的顺序产生两条相邻的边,P2就是所说的顶点。多边形的顶点一般有四种情况,如图(10)所展示的那样,分别被称为左顶点、右顶点、上顶点和下顶点:

图(10) 多边形顶点的四种类型

左顶点――P1、P2和P3的y坐标满足条件 :y1 < y2 < y3;

右顶点――P1、P2和P3的y坐标满足条件 :y1 > y2 > y3;

上顶点――P1、P2和P3的y坐标满足条件 :y2 > y1 && y2 > y3;

下顶点――P1、P2和P3的y坐标满足条件 :y2 < y1 && y2 < y3;

对于左顶点和右顶点的情况,如果不做特殊处理会导致奇偶奇数错误,常采用的修正方法是修改以顶点为终点的那条边的区间,将顶点排除在区间之外,也就是删除这条边的终点,这样在计算交点时,就可以少计算一个交点,平衡和交点奇偶个数。结合前文定义的“边”数据结构:EDGE,只要将该边的ymax修改为ymax – 1就可以了。

对于上顶点和下顶点,一种处理方法是将交点计算做0个,也就是修正两条边的区间,将交点从两条边中排除;另一种处理方法是不做特殊处理,就计算2个交点,这样也能保证交点奇偶个数平衡。

(2)      水平边的处理

水平边与扫描线重合,会产生很多交点,通常的做法是将水平边直接画出(填充),然后在后面的处理中就忽略水平边,不对其进行求交计算。

(3)      如何避免填充越过边界线

边界像素的取舍问题也需要特别注意。多边形的边界与扫描线会产生两个交点,填充时如果对两个交点以及之间的区域都填充,容易造成填充范围扩大,影响最终光栅图形化显示的填充效果。为此,人们提出了“左闭右开”的原则,简单解释就是,如果扫描线交点是1和9,则实际填充的区间是[1,9),即不包括x坐标是9的那个点。

2.2扫描线算法实现

扫描线算法的整个过程都是围绕“活动边表(AET)”展开的,为了正确初始化“活动边表”,需要初始化每条扫描线的“新边表(NET)”,首先定义“新边表”的数据结构。定义“新边表”为一个数组,数组的每个元素存放对应扫描线的所有“新边”。因此定义“新边表”如下:

510     std::vector< std::list<EDGE> > slNet(ymax - ymin + 1);

ymax和ymin是多边形所有顶点中y坐标的最大值和最小值,用于界定扫描线的范围。slNet 中的第一个元素对应的是ymin所在的扫描线,以此类推,最后一个元素是ymax所在的扫描线。在开始对每条扫描线处理之前,需要先计算出多边形的ymax和ymin并初始化“新边表”:

503 void ScanLinePolygonFill(const Polygon& py, int color)

504 {

505     assert(py.IsValid());

506

507     int ymin = 0;

508     int ymax = 0;

509     GetPolygonMinMax(py, ymin, ymax);

510     std::vector< std::list<EDGE> > slNet(ymax - ymin + 1);

511     InitScanLineNewEdgeTable(slNet, py, ymin, ymax);

512     //PrintNewEdgeTable(slNet);

513     HorizonEdgeFill(py, color); //水平边直接画线填充

514     ProcessScanLineFill(slNet, ymin, ymax, color);

515 }

InitScanLineNewEdgeTable()函数根据多边形的顶点和边的情况初始化“新边表”,实现过程中体现了对左顶点和右顶点的区间修正原则:

315 void InitScanLineNewEdgeTable(std::vector< std::list<EDGE> >& slNet,

316                               const Polygon& py, int ymin, int ymax)

317 {

318     EDGE e;

319     for(int i = 0; i < py.GetPolyCount(); i++)

320     {

321         const Point& ps = py.pts[i];

322         const Point& pe = py.pts[(i + 1) % py.GetPolyCount()];

323         const Point& pss = py.pts[(i - 1 + py.GetPolyCount()) %py.GetPolyCount()];

324         const Point& pee = py.pts[(i + 2) % py.GetPolyCount()];

325

332         if(pe.y != ps.y) //不处理水平线

333         {

334             e.dx = double(pe.x - ps.x) / double(pe.y - ps.y);

335             if(pe.y > ps.y)

336             {

337                 e.xi = ps.x;

338                 if(pee.y >= pe.y)

339                     e.ymax = pe.y - 1;

340                 else

341                     e.ymax = pe.y;

342

343                 slNet[ps.y - ymin].push_front(e);

344             }

345             else

346             {

347                 e.xi = pe.x;

348                 if(pss.y >= ps.y)

349                     e.ymax = ps.y - 1;

350                 else

351                     e.ymax = ps.y;

352                 slNet[pe.y - ymin].push_front(e);

353             }

354         }

355     }

356 }

多边形的定义Polygon和本系列第一篇《计算几何与图形学有关的几种常用算法》一文中的定义一致,此处就不再重复说明。算法通过遍历所有的顶点获得边的信息,然后根据与此边有关的前后两个顶点的情况确定此边的ymax是否需要-1修正。ps和pe分别是当前处理边的起点和终点,pss是起点的前一个相邻点,pee是终点的后一个相邻点,pss和pee用于辅助判断ps和pe两个点是否是左顶点或右顶点,然后根据判断结果对此边的ymax进行-1修正,算法实现非常简单,注意与扫描线平行的边是不处理的,因为水平边直接在HorizonEdgeFill()函数中填充了。

ProcessScanLineFill()函数开始对每条扫描线进行处理,对每条扫描线的处理有四个操作,如下代码所示,四个操作分别被封装到四个函数中:

467 void ProcessScanLineFill(std::vector< std::list<EDGE> >& slNet,

468                          int ymin, int ymax, int color)

469 {

470     std::list<EDGE> aet;

471

472     for(int y = ymin; y <= ymax; y++)

473     {

474         InsertNetListToAet(slNet[y - ymin], aet);

475         FillAetScanLine(aet, y, color);

476         //删除非活动边

477         RemoveNonActiveEdgeFromAet(aet, y);

478         //更新活动边表中每项的xi值,并根据xi重新排序

479         UpdateAndResortAet(aet);

480     }

481 }

InsertNetListToAet()函数负责将扫描线对应的所有新边插入到aet中,插入操作到保证aet还是有序表,应用了插入排序的思想,实现简单,此处不多解释。FillAetScanLine()函数执行具体的填充动作,它将aet中的边交点成对取出组成填充区间,然后根据“左闭右开”的原则对每个区间填充,实现也很简单,此处不多解释。RemoveNonActiveEdgeFromAet()函数负责将对下一条扫描线来说已经不是“活动边”的边从aet中删除,删除的条件就是当前扫描线y与边的ymax相等,如果有多条边满足这个条件,则一并全部删除:

439 bool IsEdgeOutOfActive(EDGE e, int y)

440 {

441     return (e.ymax == y);

442 }

443

444 void RemoveNonActiveEdgeFromAet(std::list<EDGE>& aet, int y)

445 {

446     aet.remove_if(std::bind2nd(std::ptr_fun(IsEdgeOutOfActive), y));

447 }

UpdateAndResortAet()函数更新边表中每项的xi值,就是根据扫描线的连贯性用dx对其进行修正,并且根据xi从小到大的原则对更新后的aet表重新排序:

449 void UpdateAetEdgeInfo(EDGE& e)

450 {

451     e.xi += e.dx;

452 }

453

454 bool EdgeXiComparator(EDGE& e1, EDGE& e2)

455 {

456     return (e1.xi <= e2.xi);

457 }

458

459 void UpdateAndResortAet(std::list<EDGE>& aet)

460 {

461     //更新xi

462     for_each(aet.begin(), aet.end(), UpdateAetEdgeInfo);

463     //根据xi从小到大重新排序

464     aet.sort(EdgeXiComparator);

465 }

其实更新完xi后对aet表的重新排序是可以避免的,只要在维护aet时,除了保证xi从小到大的排序外,在xi相同的情况下如果能保证修正量dx也是从小到大有序,就可以避免每次对aet进行重新排序。算法实现也很简单,只需要对InsertNetListToAet()函数稍作修改即可,有兴趣的朋友可以自行修改。

至此,扫描线算法就介绍完了,算法的思想看似复杂,实际上并不难,从具体算法的实现就可以看出来,整个算法实现不足百行代码。

[cpp] view plain copy
  1. #include<windows.h>
  2. #include<GL/glut.h>
  3. const int POINTNUM=7;      //多边形点数.
  4. /******定义结构体用于活性边表AET和新边表NET***********************************/
  5. typedef struct XET
  6. {
  7. float x;
  8. float dx,ymax;
  9. XET* next;
  10. } AET,NET;
  11. /******定义点结构体point******************************************************/
  12. struct point
  13. {
  14. float x;
  15. float y;
  16. } polypoint[POINTNUM]= { {250,50},{550,150},{550,400},{250,250},{100,350},{100,100},{120,30} }; //多边形顶点
  17. void PolyScan()
  18. {
  19. /******计算最高点的y坐标(扫描到此结束)****************************************/
  20. int MaxY=0;
  21. int i;
  22. for(i=0; i<POINTNUM; i++)
  23. if(polypoint[i].y>MaxY)
  24. MaxY=polypoint[i].y;
  25. /*******初始化AET表***********************************************************/
  26. AET *pAET=new AET;
  27. pAET->next=NULL;
  28. /******初始化NET表************************************************************/
  29. NET *pNET[1024];
  30. for(i=0; i<=MaxY; i++)
  31. {
  32. pNET[i]=new NET;
  33. pNET[i]->next=NULL;
  34. }
  35. glClear(GL_COLOR_BUFFER_BIT);        //赋值的窗口显示.
  36. glColor3f(0.0,0.0,0.0);             //设置直线的颜色红色
  37. glBegin(GL_POINTS);
  38. /******扫描并建立NET表*********************************************************/
  39. for(i=0; i<=MaxY; i++)
  40. {
  41. for(int j=0; j<POINTNUM; j++)
  42. if(polypoint[j].y==i)
  43. {
  44. //一个点跟前面的一个点形成一条线段,跟后面的点也形成线段
  45. if(polypoint[(j-1+POINTNUM)%POINTNUM].y>polypoint[j].y)
  46. {
  47. NET *p=new NET;
  48. p->x=polypoint[j].x;
  49. p->ymax=polypoint[(j-1+POINTNUM)%POINTNUM].y;
  50. p->dx=(polypoint[(j-1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j-1+POINTNUM)%POINTNUM].y-polypoint[j].y);
  51. p->next=pNET[i]->next;
  52. pNET[i]->next=p;
  53. }
  54. if(polypoint[(j+1+POINTNUM)%POINTNUM].y>polypoint[j].y)
  55. {
  56. NET *p=new NET;
  57. p->x=polypoint[j].x;
  58. p->ymax=polypoint[(j+1+POINTNUM)%POINTNUM].y;
  59. p->dx=(polypoint[(j+1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j+1+POINTNUM)%POINTNUM].y-polypoint[j].y);
  60. p->next=pNET[i]->next;
  61. pNET[i]->next=p;
  62. }
  63. }
  64. }
  65. /******建立并更新活性边表AET*****************************************************/
  66. for(i=0; i<=MaxY; i++)
  67. {
  68. //计算新的交点x,更新AET
  69. NET *p=pAET->next;
  70. while(p)
  71. {
  72. p->x=p->x + p->dx;
  73. p=p->next;
  74. }
  75. //更新后新AET先排序*************************************************************/
  76. //断表排序,不再开辟空间
  77. AET *tq=pAET;
  78. p=pAET->next;
  79. tq->next=NULL;
  80. while(p)
  81. {
  82. while(tq->next && p->x >= tq->next->x)
  83. tq=tq->next;
  84. NET *s=p->next;
  85. p->next=tq->next;
  86. tq->next=p;
  87. p=s;
  88. tq=pAET;
  89. }
  90. //(改进算法)先从AET表中删除ymax==i的结点****************************************/
  91. AET *q=pAET;
  92. p=q->next;
  93. while(p)
  94. {
  95. if(p->ymax==i)
  96. {
  97. q->next=p->next;
  98. delete p;
  99. p=q->next;
  100. }
  101. else
  102. {
  103. q=q->next;
  104. p=q->next;
  105. }
  106. }
  107. //将NET中的新点加入AET,并用插入法按X值递增排序**********************************/
  108. p=pNET[i]->next;
  109. q=pAET;
  110. while(p)
  111. {
  112. while(q->next && p->x >= q->next->x)
  113. q=q->next;
  114. NET *s=p->next;
  115. p->next=q->next;
  116. q->next=p;
  117. p=s;
  118. q=pAET;
  119. }
  120. /******配对填充颜色***************************************************************/
  121. p=pAET->next;
  122. while(p && p->next)
  123. {
  124. for(float j=p->x; j<=p->next->x; j++)
  125. glVertex2i(static_cast<int>(j),i);
  126. p=p->next->next;//考虑端点情况
  127. }
  128. }
  129. glEnd();
  130. glFlush();
  131. }
  132. void init(int argc,char** argv)
  133. {
  134. glutInit(&argc,argv);  //I初始化 GLUT.
  135. glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);  //设置显示模式:单个缓存和使用RGB模型
  136. glutInitWindowPosition(50,100);  //设置窗口的顶部和左边位置
  137. glutInitWindowSize(400,300);  //设置窗口的高度和宽度
  138. glutCreateWindow("Scan Program");
  139. glClearColor(1.0,1.0,1.0,0); //窗口背景颜色为白色
  140. glMatrixMode(GL_PROJECTION);
  141. gluOrtho2D(0,600,0,450);
  142. }
  143. void myDisplay(void)
  144. {
  145. glClear(GL_COLOR_BUFFER_BIT);
  146. glColor3f(0.0,0.4,0.2);
  147. glPointSize(1);
  148. glBegin(GL_POINTS);
  149. PolyScan();
  150. glEnd();
  151. glFlush();
  152. }
  153. int main(int argc,char** argv)
  154. {
  155. init(argc,argv);
  156. glutDisplayFunc(myDisplay);        //图形的定义传递给我window.
  157. glutMainLoop();
  158. return 0;
  159. }

多边形区域填充算法--扫描线填充算法(有序边表法)相关推荐

  1. 多边形填充算法-有序边表法(扫描线算法) 计算机图形学

    1.算法的基本思想(扫描线连贯性原理): 对于一个给定的多边形,用一组水平(垂直)的扫描线进行扫描,对每一条扫描线均可求出与多边形边的交点,这些交点将扫描线分割成落在多边形内部的线段和落在多边形外部的 ...

  2. 多边形填充算法-有序边表法(扫描线算法)

    1.算法的基本思想(扫描线连贯性原理): 对于一个给定的多边形,用一组水平(垂直)的扫描线进行扫描,对每一条扫描线均可求出与多边形边的交点,这些交点将扫描线分割成落在多边形内部的线段和落在多边形外部的 ...

  3. 图形学扫描线填充算法

    在上图形学课的时候,学习了扫描线填充算法.不过在完成实验的时候在真正理解了该算法,在此记录一下,如果有表达上的错误,欢迎指正! 扫描线填充算法通过在与图形相交的第(1,2).(3,4)... 边之间划 ...

  4. Java边缘填充_任意画一个多边形,用边缘填充算法填充

    任意画一个多边形,并用边(缘)填充算法进行填充.(多边形的顶点坐标存放在数组中,坐标值由键盘输入) #include #include //边缘填充 void draw(int a,int b){ f ...

  5. c++多边形扫描线填充算法_基于MATLAB的道格拉斯普克算法递归实现

    道格拉斯普克算法 (道格拉斯-普克)Douglas-Peukcer算法由D.Douglas和T.Peueker于1973年提出,是线状要素抽稀的经典算法.用它处理大量冗余的几何数据点,既可以达到数据量 ...

  6. CG-多边形扫描线填充算法详解

    目录 1.基本思想 2.算法步骤 3.例题 4.算法的优缺点 1.基本思想         对于一个多边形,用一组水平或垂直的扫描线进行扫描,求出每条扫描线与多边形的交点,这些交点将扫描线分割线段,将 ...

  7. 计算机图形学--扫描线填充算法

    目录 基本思想 有序边表思想 代码实现 基本思想 从图形的y最小值到y最大值依次进行y上的遍历,遍历的每个y=c这条直线称为扫描线.对于每条扫描线,多边形会和这条直线存在若干交点.这些交点中的之间的线 ...

  8. 扫描线填充算法(DDA应用)

    欢迎关注更多精彩 关注我,学习常用算法与数据结构,一题多解,降维打击. 本期话题:给定多边形的边如何填充多边形内部区域 朴素做法 枚举法 枚举屏幕上的每一点,判断点是否在多边形内.点在多边形内判断方法 ...

  9. c语言图形学扫描线填充算法,《计算机图形学》OpenGL实现扫描线填充算法

    顾名思义啊,就是在OpenGL中用扫描填充算法画一个稍微复杂的图形: #include #include #include #include #define COLOR_NEW 1.0,0.0,0.0 ...

最新文章

  1. DBus glib 各数据类型接收与发送详解—C语言(3)
  2. python数据框的横向贾总_[Spark][Python]DataFrame的左右连接例子
  3. 大数据导论章节答案_智慧树大数据导论章节答案
  4. Import declarations are not supported by current JavaScript version
  5. python_集合(set)
  6. 如何在Linux上使用HAProxy配置HTTP负载均衡系统
  7. CString Char* 转换 - C++学习
  8. 卸载北信源桌面监控攻略Uninstall VRV
  9. Typora下载、安装及使用
  10. 谷歌拼音输入法之初用
  11. java判断接口地址是否存在_java.util.Iterator接口中的hashNext()方法是用来判断集合中是否存在下一个元素的()_学小易找答案...
  12. echarts 画四川省地图 点击高亮并获取各市区参数
  13. android 开启wifi代码,Android编程打开WiFi
  14. Python编程学习笔记 - 下载数据进行可视化(I)
  15. 基于tesseract的文字识别
  16. 阿里云朱照远:AI打开新视界 8K时代已来! 1
  17. GeoMesa 详细介绍
  18. Android wifi信号强度与图标对应关系
  19. 牛客小白月赛10 A,B,C,D
  20. 计算机网络实验感想6,计算机网络实验项目六

热门文章

  1. 频繁跳槽的人简历是不是更容易被淘汰?
  2. 【超融合】818超融合发展经历了哪几个阶段!
  3. 计算机的运算方法(中)测试
  4. 【C语言】让你不再害怕指针——C指针详解(经典,非常详细)
  5. UbuntuWindows 双系统时间不同步,Windows 慢8个小时
  6. xmppFrameWork的使用
  7. 计算机主机光驱弹不出来怎么办,电脑光驱弹不出来怎么办 电脑光驱不出来的图文解决方法...
  8. ELF与BIN文件的生成和转换
  9. 2017 Multi-University Training Contest 1 solutions BY 北京航空航天大学
  10. OSChina 周三乱弹 ——纪念Bob Taylor