Android Region代码分析

来源:互联网 发布:巨人网络总裁 编辑:程序博客网 时间:2024/06/18 07:32

一、Region的定义和合法性检查

Android系统中,定义了Region的概念,它代表屏幕上的一个区域,它是由一个或多个Rect组成的,代码位于frameworks/native/libs/ui/Region.cpp。而Rect则代表屏幕上的一个方形区域,这个区域可能是不可见的,部分可见或者完全不可见的。从代码实现的角度来看Region的实现,它拥有一个私有的数据成员变量:mStorage,它的类型为Vector<Rect>

1. mStorage是一个有序数组,数组元素类型为Rect,除了包含构成Region区域的Rect外,还额外地包含一个元素,它就是这块区域的边界。

2. 如果Region只是一个简单的方形区域,则mStorage只包含这个Rect类型的元素。

从上述两点可知,mStorge的大小永远不可能为2,要想知道某个区域的边界大小,只需返回mStorage的最后一个元素。

3. RectRegion的关系是is-a关系,反之则不成立。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. inline  bool        isEmpty() const     { return getBounds().isEmpty(); }  
  2.    inline  bool        isRect() const      { return mStorage.size() == 1; }  
  3.   
  4.    inline  Rect        getBounds() const   { return mStorage[mStorage.size() - 1]; }  
  5.    inline  Rect        bounds() const      { return getBounds(); }  

接下来研究的话题是Region的合法性。由于Region本身是由一系列Rect组成的,所以,首先,构成的Rect本身必须是合法的。其次,构成的Rect必须以Y方向和X方向排序,Y方向优先排序。这里引入另一个概念Span,它也是一个区域的概念,也是由一个或多个Rect组成,可以认为它是一种特殊的Region, 其本身也是构成Region的一部分,事实上,一个Region可以看成是由许多Span构成的。不过这些构成SpanRect必须在Y方向上,topbottom值与其他的Rect相同,即Y方向上不能重叠,在X上方向,leftright之间的覆盖的区域不能与其他的Rect之间有重叠。基于上述的描述,要检查一个Region是否合法,就要对上述的一些要求做检查,我们可以看Regionvalidate()函数,它实际上就是这样做的:

1. 首先检查构成RegionRect本身的合法性。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ...  
  2.         if (cur->isValid() == false) {  
  3.             ALOGE_IF(!silent, "%s: region contains an invalid Rect", name);  
  4.             result = false;  
  5.         }  
  6.         if (cur->right > region_operator<Rect>::max_value) {  
  7.             ALOGE_IF(!silent, "%s: rect->right > max_value", name);  
  8.             result = false;  
  9.         }  
  10.         if (cur->bottom > region_operator<Rect>::max_value) {  
  11.             ALOGE_IF(!silent, "%s: rect->right > max_value", name);  
  12.             result = false;  
  13.         }  
  14. ...  

2. 接下来,检查这些Rect是否是有序的。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if ((*prev < *cur) == false) {  
  2.     ALOGE_IF(!silent, "%s: region's Rects not sorted", name);  
  3.     result = false;  
  4. }  

3. 然后就是检查Span的合法性

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (cur->top == prev->top) {  
  2.       if (cur->bottom != prev->bottom) {  
  3.           ALOGE_IF(!silent, "%s: invalid span %p", name, cur);  
  4.           result = false;  
  5.       } else if (cur->left < prev->right) {  
  6.           ALOGE_IF(!silent,  
  7.                   "%s: spans overlap horizontally prev=%p, cur=%p",  
  8.                   name, prev, cur);  
  9.           result = false;  
  10.       }  
  11.   } else if (cur->top < prev->bottom) {  
  12.       ALOGE_IF(!silent,  
  13.               "%s: spans overlap vertically prev=%p, cur=%p",  
  14.               name, prev, cur);  
  15.       result = false;  
  16.   }  

4. 当然,也要检查最后一个元素是不是该区域的边界。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (b != reg.getBounds()) {  
  2.       result = false;  
  3.       ALOGE_IF(!silent,  
  4.               "%s: invalid bounds [%d,%d,%d,%d] vs. [%d,%d,%d,%d]", name,  
  5.               b.left, b.top, b.right, b.bottom,  
  6.               reg.getBounds().left, reg.getBounds().top,   
  7.               reg.getBounds().right, reg.getBounds().bottom);  
  8.   }  

5. 最后,要检查一种不可能出现的情况,即mStorage的大小为2

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (reg.mStorage.size() == 2) {  
  2.         result = false;  
  3.         ALOGE_IF(!silent, "%s: mStorage size is 2, which is never valid", name);  
  4.     }  

到此为上,Region合法性的讨论就结束了。

最后总结一下:前面主要引入三个概念: Rect, Span, Region,它们之间的区别如下 :


二、RegionBoolean操作

RegionBoolean操作总体主要分主要有如下几种:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. enum {  
  2.     op_nand = region_operator<Rect>::op_nand,  
  3.     op_and  = region_operator<Rect>::op_and,  
  4.     op_or   = region_operator<Rect>::op_or,  
  5.     op_xor  = region_operator<Rect>::op_xor  
  6. };  

下面我们主要以op_or操作为情景,分析Region如何执行这些boolean操作的。显然,Region可以与RegionRect之间进行上述的boolean操作。当然,执行这些操作后,Region可能会变得不合法了,需要进行调整使新的Region变为合法的,整个过程就会伴随着怎样将Region从不合法的状态调整成合法的状态,这个过程会涉及到Rect的合并或分解。

下面我们将分析boolean_operation(...)函数的执行过程,因为所有的这些boolean操作都是基于此函数实现的。我们直接进入关键代码段:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. size_t lhs_count;  
  2.     Rect const * const lhs_rects = lhs.getArray(&lhs_count);  
  3.   
  4.     region_operator<Rect>::region lhs_region(lhs_rects, lhs_count);  
  5.     region_operator<Rect>::region rhs_region(&rhs, 1, dx, dy);  
  6.     region_operator<Rect> operation(op, lhs_region, rhs_region);  
  7.     { // scope for rasterizer (dtor has side effects)  
  8.         rasterizer r(dst);  
  9.         operation(r);  
  10.     }  

我们将上述分为三步:

1. region_operator<Rect> operation(op, lhs_region, rhs_region);

      这一步是初始化,为第二步做准备。传递了两个信息:Region进行的什么操作,以及操作的两个Region对象,这两个Region对象的引用被传递给了Spanner对象。region_operator这个类定义两个Region之间的boolean操作的步骤,其中定义的内部类region_rasterizer主要作用就是将一个Rect加入到当前的Region中,其中会涉及到SpanRect之间的合并。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. class region_rasterizer {  
  2.        friend class region_operator;  
  3.        virtual void operator()(const RECT& rect) = 0;  
  4.    public:  
  5.        virtual ~region_rasterizer() { };  
  6.    };  

2. rasterizer r(dst);

rasterrizerRegion类中内部类,它继承自上面提到的region_rasterizer类。主要实现了其中的operator()(const RECT& rect)虚函数。它对Region进行了一些初始化,该Region将是执行boolean操作后的结果Region

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. rasterizer(Region& reg)   
  2.        : bounds(INT_MAX, 0, INT_MIN, 0), storage(reg.mStorage), head(), tail(), cur() {  
  3.        storage.clear();  
  4.    }  

3. operation(r);

这步进入了实际的操作过程,将执行如下的函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void operator()(region_rasterizer& rasterizer) {  
  2.        RECT current;  
  3.        do {  
  4.            SpannerInner spannerInner(spanner.lhs, spanner.rhs);  
  5.            int inside = spanner.next(current.top, current.bottom);  
  6.            spannerInner.prepare(inside);  
  7.            do {  
  8.                TYPE left, right;  
  9.                int inside = spannerInner.next(current.left, current.right);  
  10.                if ((op_mask >> inside) & 1) {  
  11.                    if (current.left < current.right &&   
  12.                            current.top < current.bottom) {  
  13.                        rasterizer(current);  
  14.                    }  
  15.                }  
  16.            } while(!spannerInner.isDone());  
  17.        } while(!spanner.isDone());  
  18.    }  

在详细分解这个函数的执行过程之前,我们简单描述下SpannerSpannerInner这两个类的作用。Spanner相当于Region内部Span集合的迭代器,它会从Y轴增长的方向逐个迭代Span;SpannerInner则相当于某个Span的内部迭代器,它会从X轴增长的方向迭代包含于这个Span内的Rect对象。

下面, 描述这个函数的执行步骤:

1. int inside = spanner.next(current.top, current.bottom);

      这步首先会决定当前迭代的Span,以current.top, current.bottom来指定当前所处的Span。另外,也会根据inside得到两个Region之间的相对位置信息,其实质是比较两个Region的第一个Span的相对位置关系:首先,看它们的top值,然后是bottom值。如果这两个RegionY轴方向有重叠,就会发生Span的在Y轴的分解,并通过更新current.top, current.bottom记录下当前所处的新Span

2. spannerInner.prepare(inside);

根据上一步得到的两个Region的相对位置信息,来决定X轴方向迭代的起始值。

3. 进入循环,直到当前Span内的Rect迭代结束。

int inside = spannerInner.next(current.left, current.right);

这步每执行一次会更新当前的current.left, current.right的值,如果两个RegionX轴方向上有重叠,就会在Span内部发生Rect的分解,并通过更新current.left, current.right记录下当前所处的新的Rect。根据Region执行的boolean操作的语义,以决定当前所指的Rect是否应该加入到操作后的结果Region中去,即

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if ((op_mask >> inside) & 1) {  
  2.                   if (current.left < current.right &&   
  3.                           current.top < current.bottom) {  
  4.                       rasterizer(current);  
  5.                   }  
  6.               }  

下图是两个Region执行合并操作时的过程示意图:


最后结果中,有三个Span,第一个Span包含Rect 1, 第二个Span包含Rect 2,3,4, 第三个Span中包含Rect 5。不过上述也只是中间结果,在执行rasterizer(current);之后,才是最终的结果,所以我们接着看下rasterizer(current)的执行过程。根据C++虚函数的多态性,这个调用实际会执行到Region::rasterizer类的 operator()(const Rect& rect) 方法,来看下它的具体实现过程:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. virtual void operator()(const Rect& rect) {  
  2.        //ALOGD(">>> %3d, %3d, %3d, %3d",  
  3.        //        rect.left, rect.top, rect.right, rect.bottom);  
  4.        if (span.size()) {  
  5.            if (cur->top != rect.top) {  
  6.                flushSpan();  
  7.            } else if (cur->right == rect.left) {//two rect connected and will merge into one rect.  
  8.                cur->right = rect.right;  
  9.                return;  
  10.            }  
  11.        }  
  12.        span.add(rect);  
  13.        cur = span.editArray() + (span.size() - 1);  
  14.    }  

简单描述下上述函数所反映的逻辑:如果传入的Rect对象是当前Span的第一个Rect对象,则直接将其加入到向量span中,对于第二个及之后加入的Rect,则进行这样的判断,如果当前Rect对象的top值不等于当前Spantop值,说明是一个新的Span开始,则首先需要通过fushSpan()将之前Span加入到结果Region中去,可能会涉及到合并的操作,主要是指相邻两个Span之间的合并;如果当前Rect对象还属于同一个Span,则看这个Rect是否可以与相邻的Rect进行合并。

4. 最后一步,执行Region::rasterizer类的析构函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ~rasterizer() {  
  2.     if (span.size()) {  
  3.         flushSpan();  
  4.     }  
  5.     if (storage.size()) {  
  6.         bounds.top = storage.itemAt(0).top;  
  7.         bounds.bottom = storage.top().bottom;  
  8.         if (storage.size() == 1) {  
  9.             storage.clear();  
  10.         }  
  11.     } else {  
  12.         bounds.left  = 0;  
  13.         bounds.right = 0;  
  14.     }  
  15.     storage.add(bounds);  
  16. }  

首先,执行最后一次flushSpan,确保所有的Span都加入到了结果Region中,当然,也会执行必要的合并。最后,根据Region合法性的要求,将Region的边界作为一个Rect对象加入到结果Region中。所以,最后,我们看到的结果Region是这样的:


三、T-Junction消除

T-Junction问题是图像渲染中的经常碰到的一个问题,特别是3D Graphics Rendering技术中,T-Junction消除是其中的一个研究课题。那什么是T-Junction问题呢?

下面是对T-Junction问题的描述:

“A T-Junction is a spot where two polygons meet along the edge of another polygon”

如:


另一种表述为:

“The location where a vertex of one polygon lies on the edge of another polygon is called a T-Junction”


T-Junction会产生什么后果呢,我们先看下Android代码中的描述:

“avoid T-junctions as they cause artifacts in between the resultant geometry when complex transforms occur.”

我的理解是因为图像渲染过程中会基于顶点进行插值,顶点A处的插值点在图形转换后,并不能保证与顶点A完全重合,所以在生成的图像中T-Junction处产生亮点,与周围像素不协调。下面我们重点看Android源码是怎样进行T-Junction消除的。

Region类中,专门定义了一个函数:createTJunctionFreeRegion,它对一个含有T-JunctionRegion进行修改,使之变成没有T-JuncionRegion。最终结果会出现对一些Span的分解。

根据RegionTest.cpp中的checkVertTJunction函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void checkVertTJunction(const Rect* lhs, const Rect* rhs) {  
  2.        EXPECT_FALSE((rhs->right > lhs->left && rhs->right < lhs->right) ||  
  3.                 (rhs->left > lhs->left && rhs->left < lhs->right));  
  4.     }  

我们可以看到Android视如下几种情况为T-Juction:


在了解了存在T-Junction的几种存在情况后,我们来看具体是怎样消除T-Junction的:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Region Region::createTJunctionFreeRegion(const Region& r) {  
  2.     if (r.isEmpty()) return r;  
  3.     if (r.isRect()) return r;  
  4.   
  5.     Vector<Rect> reversed;  
  6.     reverseRectsResolvingJunctions(r.begin(), r.end(), reversed, direction_RTL);  
  7.   
  8.     Region outputRegion;  
  9.     reverseRectsResolvingJunctions(reversed.begin(), reversed.end(),  
  10.             outputRegion.mStorage, direction_LTR);  
  11.     outputRegion.mStorage.add(r.getBounds()); // to make region valid, mStorage must end with bounds  
  12.   
  13. #if VALIDATE_REGIONS  
  14.     validate(outputRegion, "T-Junction free region");  
  15. #endif  
  16.   
  17.     return outputRegion;  
  18. }  

可以看到,具体执行T-Junction消除的函数是reverseRectsResolvingJunctions,而且被调用了两次,这其实也反映了消除T-Junction过程中的步骤,在这个过程中,需要对Region按以Span为单位进行两次扫描,第一次从Y轴减小的方向扫描,第二次,从Y轴增长的方向扫描。每次扫描,都会将T-Junction点消除,进行两次扫描的原因是因为每次扫描只能消除上述的5种情况。下图是T-Junction点消除后的情况:


红色虚线是分解边。可以看到,这个过程会产生许多新的Rect

四、测试与验证

前面三部分是理论部分,主要是通过阅读源码得到的一些步骤和过程,下面将通过测试程序来验证我们的理论,看我们的理解是否正正确:

1. 验证Regionboolean操作。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void test2()  
  2. {  
  3.      Region r;  
  4.      r.clear();  
  5.      r.orSelf(Rect(0, 0, 2, 2));  
  6.      r.orSelf(Rect(1, 1, 3, 3));  
  7.      dump(r, "A|B");  
  8.      echo("--------------");  
  9.   
  10.      r.clear();  
  11.      r.orSelf(Rect(0, 0, 2, 2));  
  12.      r.xorSelf(Rect(1, 1, 3, 3));  
  13.      dump(r, "A xor B");  
  14.      echo("----------------------------");  
  15.   
  16.      r.clear();  
  17.      r.orSelf(Rect(0, 0, 2, 2));  
  18.      r.subtractSelf(Rect(1, 1, 3, 3));  
  19.      dump(r, "A-B");  
  20.      echo("---------------------");  
  21. }  

输出结果:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Region: A|B, count = 3  
  2.   
  3. [  0,   0,   2,   1]  
  4.   
  5.  [  0,   1,   3,   2]  
  6.   
  7.  [  1,   2,   3,   3]  
  8.   
  9.  ----------------------  
  10.   
  11. Region: A xor B, count = 4  
  12.   
  13. [  0,   0,   2,   1]  
  14.   
  15.  [  0,   1,   1,   2]  
  16.   
  17.  [  2,   1,   3,   2]  
  18.   
  19.  [  1,   2,   3,   3]  
  20.   
  21.  ----------------------  
  22.   
  23. Region: A-B, count = 2  
  24.   
  25. [  0,   0,   2,   1]  
  26.   
  27.  [  0,   1,   1,   2]  
  28.   
  29.  ----------------------  

结果完全符合预期。


2. 验证T-Junction的消除结果是否与我们的预期一致。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void test1()  
  2. {  
  3.      Region r;  
  4.      r.clear();  
  5.      r.orSelf(Rect(1, 0, 2, 1));  
  6.      r.orSelf(Rect(0, 1, 3, 2));  
  7.      dump(r, "1");  
  8.      echo("----------------------------");  
  9.      Region modified = Region::createTJunctionFreeRegion(r);  
  10.      dump(modified, "1'");  
  11.      echo("------------------------");  
  12.   
  13.      r.clear();  
  14.      r.orSelf(Rect(0, 0, 1, 1));  
  15.      r.orSelf(Rect(0, 1, 2, 2));  
  16.      dump(r, "2");  
  17.      echo("-------------------------");  
  18.      modified = Region::createTJunctionFreeRegion(r);  
  19.      dump(modified, "2'");  
  20.      echo("-----------------------------");  
  21.   
  22.      r.clear();  
  23.      r.orSelf(Rect(0, 0, 2, 1));  
  24.      r.orSelf(Rect(1, 1, 3, 2));  
  25.      dump(r, "3");  
  26.      echo("-------------------");  
  27.      modified = Region::createTJunctionFreeRegion(r);  
  28.      dump(modified, "3'");  
  29.      echo("--------------------------");  
  30.   
  31.      r.clear();  
  32.      r.orSelf(Rect(1, 0, 2, 1);  
  33.      r.orSelf(Rect(0, 1, 2, 2));  
  34.      dump(r, "4");  
  35.      echo("------------------------");  
  36.      modified = Region::createTJunctionFreeRegion(r);  
  37.      dump(modified, "4'");  
  38.      echo("------------------------");  
  39.   
  40.      r.clear();  
  41.      r.orSelf(Rect(1, 0, 3, 1));  
  42.      r.orSelf(Rect(0, 1, 2, 2));  
  43.      dump(r, "5");  
  44.      modified = Region::createTJunctionFreeRegion(r);  
  45.      dump(modified, "5'");  
  46.      echo("--------------------------");  
  47.   
  48. }  

输出结果:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Region: 1, count = 2  
  2.   
  3. [  1,   0,   2,   1]  
  4.   
  5.  [  0,   1,   3,   2]  
  6.   
  7.  ----------------------  
  8.   
  9. Region: 1', count = 4  
  10.   
  11. [  1,   0,   2,   1]  
  12.   
  13.  [  0,   1,   1,   2]  
  14.   
  15.  [  1,   1,   2,   2]  
  16.   
  17.  [  2,   1,   3,   2]  
  18.   
  19.  ----------------------  
  20.   
  21. Region: 2, count = 2  
  22.   
  23. [  0,   0,   1,   1]  
  24.   
  25.  [  0,   1,   2,   2]  
  26.   
  27.  ----------------------  
  28.   
  29. Region: 2', count = 3  
  30.   
  31. [  0,   0,   1,   1]  
  32.   
  33.  [  0,   1,   1,   2]  
  34.   
  35.  [  1,   1,   2,   2]  
  36.   
  37.  ----------------------  
  38.   
  39. Region: 3, count = 2  
  40.   
  41. [  0,   0,   2,   1]  
  42.   
  43.  [  1,   1,   3,   2]  
  44.   
  45.  ----------------------  
  46.   
  47. Region: 3', count = 4  
  48.   
  49. [  0,   0,   1,   1]  
  50.   
  51.  [  1,   0,   2,   1]  
  52.   
  53.  [  1,   1,   2,   2]  
  54.   
  55.  [  2,   1,   3,   2]  
  56.   
  57.  ----------------------  
  58.   
  59. Region: 4, count = 2  
  60.   
  61. [  1,   0,   2,   1]  
  62.   
  63.  [  0,   1,   2,   2]  
  64.   
  65.  ----------------------  
  66.   
  67. Region: 4', count = 3  
  68.   
  69. [  1,   0,   2,   1]  
  70.   
  71.  [  0,   1,   1,   2]  
  72.   
  73.  [  1,   1,   2,   2]  
  74.   
  75.  ----------------------  
  76.   
  77. Region: 5, count = 2  
  78.   
  79. [  1,   0,   3,   1]  
  80.   
  81.  [  0,   1,   2,   2]  
  82.   
  83.  ----------------------  
  84.   
  85. Region: 5', count = 4  
  86.   
  87. [  1,   0,   2,   1]  
  88.   
  89.  [  2,   0,   3,   1]  
  90.   
  91.  [  0,   1,   1,   2]  
  92.   
  93.  [  1,   1,   2,   2]  
  94.   
  95.  ----------------------  

这个结果也符合预期。

3. 练习题

构造如下Region

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Region r;  
  2.   // |xxxx   |  
  3.   // | xxxx  |  
  4.   // |  xxxx |  
  5.   // |   xxxx|  
  6.  for (int i = 0; i < 4; i++) {  
  7.      r.orSelf(Rect(i,i,i+4,i+1));  
  8.  }  

消除T-Junction前有4Rect, 消除T-Junction后有16Rect


后记:

1. 获知Region当前Rect的数量以及存放Rect的数组:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. SharedBuffer const* Region::getSharedBuffer(size_t* count) const {  
  2.     // We can get to the SharedBuffer of a Vector<Rect> because Rect has  
  3.     // a trivial destructor.  
  4.     SharedBuffer const* sb = SharedBuffer::bufferFromData(mStorage.array());  
  5.     if (count) {  
  6.         size_t numRects = isRect() ? 1 : mStorage.size() - 1;  
  7.         count[0] = numRects;  
  8.     }  
  9.     sb->acquire();  
  10.     return sb;  
  11. }  

其中,通过SharedBuffer::bufferFromData获取Region实际存取Rect的Buffer,记录下此ShareBuffer,

sb->data() == mStorage.array()

一般需要记录下sb->data()返回的地址,以便通过它能获取对应的ShareBuffer对象,最后调用它的release方法释放Buffer。


0 0
原创粉丝点击