iOS图形学(二):bitmap位图详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS图形学(二):bitmap位图详解相关的知识,希望对你有一定的参考价值。
参考技术A概念: 所谓的 BitMap 算法就是用一个 bit 位来标记某个元素所对应的 value;
举例:
现有 40 亿个整数,当给定一个新的整数时,判断新的整数在这40亿个数字中是否存在,假设该架构下整形为4个字节;
其实这个问题的性能点有两方面:
假设 40E 个整数存储在磁盘中,也就是 40亿*4字节,约等于16GB。
假设系统运行内存为2GB,每次新来一个数字就循环加载 16GB 进入系统,和新的整数进行对比。
这种方法每次来一个新的数字进行判断时,因为内存不够用,每次都需要 8 次 i/O 操作,且数据量巨大(2GB),时间上会达到小时级别;
最久时间消耗:8次I/O + 40E次对比运算;
改进方法1,使用 8 个 2GB 运行内存的电脑加载 16GB 的数据,这样只需要并行加载,相当于只加载一次的时间,然后 8 台计算机对比新的整数,最后对结果进行汇总;
此时,因为 40E 个整数都已经被加载进入了内存,每来一个新的数字进行对比时,不需要再进行 I/O 操作,只需要在 8 台机器中并行对比即可;
最久时间消耗:1次I/O(一次性) + 40E/8 次对比运算;
上述两种方法的计算消耗比较高,接下来可以使用位图算法来针对计算消耗进行优化。
int 为 4 个字节,范围为:-2147483648 ~ 2147483647,也就是4294967296个数字。使用一个 bit 也就是一个二进制位来代表一个数字,那么需要 4294967296 / 8 /1024 / 1024 / 1024 约等于 0.5 GB(bit->byte->kb->m->gb)。
也就是说,如果使用内存中一个二进制位来代表一个数字,那么只需要开辟 0.5G 的内存就可以表示所有的整数,此时将 int 类型的内存消耗转化成了 BOOL 类型的内存消耗,所以内存消耗缩小了 32 倍。
I/O 消耗仍然无法避免,但是一个整数在内存中的占用小了 32 倍,只需要一台机器,且不用循环 I/O 了。在内存中开辟 0.5G 内存之后,只需要循环读取 40亿 个数字,然后再内存中对应的 bit 位进行标记,也就是标记为 1,最后根据指定的整数,取出内存中偏移地址的比特位的值,如果为 1 则表示存在,否则反之。此时,计算消耗为 1 次;
最久耗时:1次I/O(一次性) + 1次位运算;
用特定大小的内存空间来表示单个像素的色值,以逐行扫描的方式来来表示整张图片中所有的像素的色值;
在《计算机图形学基础第四版》中对 bitmap 的描述如下:
总结一下其观点,针对帧缓存(Frame Buffer)而言:
个人理解:其实,位图的概念已经没有那么拘泥于是否一定要使用 1 个比特位了。bitmap 的概念就是使用矩阵的方式来表示整体数据,以此来减少数据大小(算法)或则是实现某一目的(raster)。当然,严谨一点更好,所以 Frame Buffer 最好描述为 pixmap(像素图);
从纯数学的角度,任何一个面都由无数个点组成。但从生理角度而言,人类的肉眼无法区分很小的点。所以在实际应用中,我们没有必要用无数个点来表示一张图片,甚至都没有必要使用足够多的点,只需要让点的个数和大小在人眼能区分的极限之上一点点就好了,如此就能在清晰度和节约内存之间达到平衡。
其一,是因为太多点,人眼也看不出来差别,这就是很多人觉得 2k、4k 好像和 1024*768 并没有太大差别的原因。
其二,使用无数点来表示一张图片是不可能的,使用很大数量的点是极其耗费资源的,性价比很低。
最终决定使用一定数量的点来表示一张图片,而这个点就是像素。而视频本质上时无数图片的连贯播放,因此像素成了图形科学的基础单位;
显示器成像基本上是逐行扫描,如图所示:
再根据上述的位图原理,位图算法可以极大节省空间或者是减少运算量。而像素的数量较多,假设有 1024*768 个像素点,像素点使用我们最常用的16 进制 RGB 来表示颜色,一个像素点有 256 * 256 * 256 种可能。假设一个 Int 类型占 4 字节,也就是 32 bit,那么一个像素点占用的内存空间就是:32 bit。而采用位图之后单个像素占用的存储空间为:8bit * 3 = 24 bit,节省的空间就是 24bit * 像素个数。
上述计算看上去节省的空间不大,甚至如果是 ARGB 的颜色,那么同样也是 32bit ,相比于使用 4 字节的 int 来存储,好像并没有太大优化,但是这里涉及到几个问题:
综上,所以最终采用位图的方式来表示像素点。
压缩会在后文中讨论,这里从色深就可以很直观的知道,8bit 的图片必定没有 32bit 的图片颜色丰富。
还需要补充一个概念:通道
一般 ARGB 颜色就是 4个通道,分别是 透明度、R、G、B,每个通道都使用一定的位数来表示,比如 ARGB_8888 就是 4 通道,每条通道 8 bit。比如 8bit 的灰度颜色,只有黑白,属于单通道。
安卓中,使用 Config 表示每个像素点对 ARGB 通道值的存储方案:
ARGB_8888:每个通道值采8bit来表示,每个像素点需要4字节的内存空间来存储数据。该方案图片质量是最高的,但是占用的内存也是最大的;
ARGB_4444:每个通道都是4位,每个像素占用2个字节,图片的失真比较严重。一般不用这种方案。
RGB_565:这种方案RGB通道值分别占5、6、5位,但是没有存储A通道值,所以不支持透明度。每个像素点占用2字节,是ARGB_8888方案的一半。
ALPHA_8:这种方案不支持颜色值,只存储透明度A通道值,使用场景特殊,比如设置遮盖效果等。
比较分析:一般我们在ARGB_8888方式和RGB_565方式中进行选取:不需要设置透明度时,比如拍摄的照片等,RGB_565是个节省内存空间的不错的选择;既要设置透明度,对图片质量要求又高,就用ARGB_8888;
ios 中常见的颜色为三种:
上述三种颜色,在设置 bitmap context 中的颜色时,又分为不同参数设置:
cs:color space
bpp:bit per pixel
bpc:bit per component
常量的含义:
颜色布局:
图片的存储一般有两种形式:未加载进内存和加载进内存后;内存中都是以 bitmap 方式表达,非内存中,可以是 bitmap,也可以是各种压缩格式。其中,在内存中的 bitmap 格式称为实际大小;
特点:
特点:
图片格式一般有几种:
bitmap 的大小为:
size = width * height * bpp(bit per pixel)
平常我们见到的图片都是被压缩过后的图片,所以其大小会比 bitmap 格式的图片大小小很多;但是 PNG 格式的图片被加载进内存之后会被还原成全量 bitmap ,所以这也是很多重度使用图片的 app 的一个优化方向;
图元文件并不是矢量图,因为它存储的并不是矢量信息。
它存储的是你在绘图的时候调用了哪些操作。
如果是位图文件记录了你的画布上每一点的颜色的话,
图元文件就是纪录的你如何下笔的。
bitmap 最终数据,被加载到 frame buffer (显存)后,直接由显示器加载数据并最终显示到屏幕上;
布隆过滤器详解
今天学习一个有用的算法:布隆过滤器 ( Bloom Filter )。
布隆过滤器是 1970 年由 Burton Howard Bloom 提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于快速检索一个元素是否在一个集合中。
先来了解一中数据结构——位图,因为布隆过滤器本身就是基于位图的,是对位图的一种改进。
位图
这里说的位图 ( BitMap ) 不是图形学中的像素图片,而是内存中连续的二进制位 (Bit ),用于对大量整形数据做去重和查询。
我们来举一个例子,假设有 1 千万个整数,取值范围都在 1 - 1 亿之间,如何快速判断某一个整数是否在这 1 千万个整数之中呢。我们当然可以用散列表来解决,但是今天我们来用位图解决一下。
先申请一个长度为 1 亿的 boolean[] 数组,我们把这 1 千万个整数作为数组下标,将对应的数组值设置为 true 。比如,整数5对应的就是 arr[5] = true。
当我们查询某个数 K 是否在这 1 千万个整数中时,只需要取出 arr[K] 看其是否为 true 即可。
当然在大多数编程语言中,boolean 是用 byte 实现的,1 亿个字节的数组所占内存空间为 95.37MB。我们知道一个字节等于 8 个比特 ( Bit ),事实上,标识 boolean 变量我们只用一个 Bit 就可以,1 代表 true, 0 代表 false。
这就对应着一个叫做位图的数据结构,这并不需要我们自己实现,JDK 的标准类库中的 BitSet 已经帮我们做好了这一点,其原理是在内部维护者一个 long 类型的数组,所以 BitSet 的最小空间为 64Bit ( long 占 8 字节 64Bit 大小),通过封装来进行按位操作,并且支持自动扩容。
此时 1 亿个元素的数组所占的内存空间就缩减到了 12MB。但是前提是我们假设的数字范围是 1 - 1 亿,假如数字范围是 1 - 10 亿呢?那位图的大小就是 10 亿个 Bit 位,也就是 120MB 的大小,消耗的内存空间不降反增。为了解决这个问题,布隆过滤器就出场了。
布隆过滤器
针对上述 1 - 10 亿的问题,我们仍然使用 1 亿个大小的位图,然后通过哈希函数,对数字进行处理,让其映射到 1 - 1 亿的区间内。当然哈希函数的哈希冲突是不可避免的,不过我们可以设法减少这种冲突,布隆过滤器的做法是采用多个哈希函数。
我们使用 K 个哈希函数对同一个数字进行求值,将会得到 K 个不同的哈希值 X1,X2 ... Xk,将这 K 个值作为位图中的下标,将位图中的 arr[X1],arr[X2] ... arr[Xk] 都设置为 true ,也就是说用 K 个二进制位标识一个数字存在。
这样我们在查询某个数字是否存在的时候,我们对这个数字分别用这 K 个哈希函数分别求值,若 arr[X1],arr[X2] ... arr[Xk] 其中任意一个不为 true,则说明该数字不存在于集合中。若都为 true,则极有可能在集合中,因为哈希冲突的存在我们对两个不同的数字求哈希值,这两个值有可能重复。
布隆过滤器的原理是,当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组中的 K 个点,把它们置为 true。检索时,我们只要看看这些点是不是都是 true 就(大约)知道集合中有没有它了:如果这些点有任何一个为 false ,则被检元素一定不在;如果都是 true,则被检元素很可能在。即布隆过滤器判定一个元素不在集合中则一定不存在,若判定在集合中则可能存在。
需要注意的是,布隆过滤器不允许 remove 元素,因为那样的话会把相应的 K 个比特位位置为 false ,而其中很有可能有其他元素对应的位。
小结
以爬虫爬取的 URL 判重为例,假设有 10 亿个 URL 待判断,每个URL平均长度为 64 字节,我们可以用一个 10 倍的位图来存储,也就是大约 1.2GB,而换成散列表的话考虑到哈希冲突,装载因子不能过小,至少也需要 100GB 的空间,所以布隆过滤器在存储空间的消耗上降低了非常多。
效率方面,布隆过滤器的一次匹配过程涉及到多个哈希函数的计算属于 CPU 密集型,而散列表进行内存中的数据的多次匹配,属于内存密集型,CPU 的运算速度比内存快的多,所以理论上布隆过滤器更加高效。
运用布隆过滤器需要对误判有一定容忍度,如垃圾邮件识别、爬虫 URL 去重、防止缓存击穿等场景。同时建议维护一个小的白名单用于防止误判。
不过布隆过滤器作为一种概率模型,我们可以通过设置合理的哈希函数个数和位图的大小来尽可能降低这种误判几率,已经有经过证明的公式可以参考,此处略去公式和证明过程。
实际运用中,我们并不需要自己实现一个布隆过滤器,Google 的 Guava 类库中提供了 BloomFilter 供我们使用,可以直接设定好误判率后直接交给 Guava 进行处理。
[参考]
[1]《数学之美》 吴军著
[2] 《数据结构与算法之美》 王争
以上是关于iOS图形学(二):bitmap位图详解的主要内容,如果未能解决你的问题,请参考以下文章