SDL_Surface
是用于存储图像,可以用于图像绘制的结构体。
这里我们来看一下SDL2中的SDL_Surface
结构体和与其有关的函数操作。
SDL_Surface结构体
其实操作SDL_Surface
的函数有很多,而且都很简单。但是如果不先认识SDL_Surface
这个结构体的话,函数上的学习会比较困难。
|
|
本来还有Uint32 flags,int locked, void* locked_data, SDL_BlitMap* map
四个属性的,但是这四个属性是SDL内部使用的,所以我们就不去在意了。
接下来对上面列出的每一个属性进行详细的解释。
format
format属性存储着像素有关的格式,是一个很重要的结构体:
|
|
有一些内部属性还是有用的,如果会用到的话我会说。
format
首先是format
属性,他的值是SDL_PixelFormatEnum
枚举类型里面的一个。看了这个枚举类型相信你就知道这个属性是做什么的了,完整的在这里:
SDL_PIXELFORMAT_RGB332
SDL_PIXELFORMAT_ARGB4444
SDL_PIXELFORMAT_RGBA8888
SDL_PIXELFORMAT_UYVY
没错这一个个枚举常量就是表示像素点在内存中的存储方式,比如我们最熟悉的SDL_PIXELFORMAT_RGBA8888
就是表示本像素有四个分量R,G,B,A
,并且这四个分量的存储顺序是RGBA
,每个分量占8Bits。那么以此类推,SDL_PIXELFORMAT_ABGR8888
就是A
分量在最前面存储,R
分量在最后存储。SDL_PIXELFORMAT_RGB332
就是只有三个分量R,G,B
,并且R
占3Bits,G
占3Bits,B
占2Bits。
YUV颜色空间同理。
这里还有比较特殊的SDL_PIXELFORMAT_INDEX8
,其实这个是指只有RGB三分量,每个分量8位的存储方式,如果有对应的枚举类型的话应该命名为SDL_PIXELFORMAT_RGB888。
如果你想要以人类可读的方式查看自己的surface是怎么存储图像的话,这里有一个const char* SDL_GetPixelFormatName(Uint32 format)
函数,你可以把format
放进去,他会返回给你一个和枚举类型一模一样名称的字符串。
palette
这个是调色板,也是一个枚举类型:
|
|
调色板有说明用呢?这里还得说一下位图的存储方式。需要注意的是:SDL_Surface本身只能存储位图数据(因为其官方只给了SDL_LoadBMP()函数来加载位图,而没有函数去加载其他格式的图片),虽然有SDL_Image库,但那是第三方的不算在讨论范围内,所以我们首先得搞清楚调色板在位图中的用途。
调色板在位图中的用途
位图里面也有一个称为调色板的东西,和SDL_Surface里面的很像,具体作用是这样的: 首先位图中每个像素可以由8Bits, 16Bits, 32Bits, 64Bits等长度的位存储。其中小于24位的位图需要调色板,大于等于24位的位图没有调色板(所以如果你的Surface->BitsPerPixel>=24的话palette属性就是NULL)。之所以有调色板是为了减少存储空间设计的。
在我们的印象中,图片应该是这样存储的(假设以RGB888格式存储):
这也是OpenCV这种库存储的方式:以R,G,B三种分量值的循环来存储。
对于BitsPerPixels>=24的位图的确是这样存储的,但是这样存储的话每个像素点就要花费$24(或者更多)*3=72bits$。那么计算机科学家就想出,能不能有一种方法在不减少像素的情况下存储少量的数据。这个方法就是调色板。
调色板中记录了这个图片中所有要用到的颜色(对于256色位图,就会记录256色),这也就是ncolors
属性的作用。然后会将所有属性以RGBxxx
(例子里面是RGB888)的方式存储在colors
属性中。
然后原本的像素就不再以RGB888
方式存储了,其会存储一个索引,这个索引指向colors
属性中的颜色:
这样本来一个像素点需要3*8=24位的,现在只要8位就OK了,虽然加了调色板,但是每个像素减少了3倍大小。
所以:如果像素以24位一下存储,像素存储的是其值在调色板中的索引,如果以24位以上(包括24位)存储那么直接存储颜色数据,没有调色板
BitsPerPixel,BytesPerPixel
这两个属性就是表示像素点以多大的内存空间存储。
掩码
掩码的话是这样的:
- 如果是带有调色板的位图(24位以下),那么掩码默认为0(因为颜色值都存储在调色板中了)
- 如果是没有调色板的位图,那么如果想要得到其像素点存储的颜色值,需要先和掩码做逻辑和运算。以
RGBA8888
格式为例:假设一个像素点的值是0xEA124256
,想要取出来R分量,首先和Rmask
(这里是0xFF000000
)做逻辑和,得到0xEA000000
,然后就需要用到一些内部属性了:再将结果右移Rshift
,再左移Rloss
长度即可得到最后的值。也就是说整个过程可以这样写:1
temp = pixel & fmt->Rmask;
temp = temp >> fmt->Rshift; temp = temp << fmt->Rloss; red = (Uint8)temp; ```
宽度和高度
这个没什么好说的,就是图像的宽度和高度。
pitch
这个值保存着一行有多少个像素(以Bytes计),比如你的图像是RGB888
存储的,假设pitch=24
,由于是24位以下的位图,所以每个像素用8位保存,也就是每个像素用1Byte保存,那么一行就是24个像素。
因为图像虽然显示是二维的,但是在内存中保存是一维的,所以必须知道一行存储了多少像素我们才能遍历整个点。
pixels
这个就是实际存储像素值的数组,24位以下存储调色板索引,24位以上直接存值
userdata
这个用户可以自己随意放入数据或者读取数据,默认为NULL。
clip_rect
这个是在你使用SDL_BlitSurface()
这样的绘图函数时,指定实际会绘制到目的地的图像范围。
可以使用SDL_SetClipRect()
来改变,默认为全部图片.
refcount
这个是引用计数,是SDL函数在对Surface操作的时候会设置,我们不用关心。
对SDL_Surface结构体的操作
接下来要展示以下如何对RGB888
存储的SDL_Surface
进行操作。我们会在上面绘制一条线,并且将绘制了线的图像保存下来。
首先我们要有一个符合格式的位图:
然后我们开始操作:
|
|
最后保存的图像是: