Android Bitmap入门:getPixels的正确理解

说在前面的话

图片处理,音频,视频处理,最复杂的就是算法。而在处理这些算法之前,最基本的功夫,就是你必须先深刻地了解自己所使用的平台,比如Android系统提供了哪些函数,这些函数的参数都有些参数,该如何使用这些函数。

Bitmap也提供了getPixel函数,可以获取单独的一个像素的数据。不过如果你曾经有过图像开的经验就应该知道,真正进行图片处理时,对像素一个个进行读取写入等操作极少(取色器只需要读取一个像素,这是我想到的一个例外)。为什么呢?因为在视频,图像,音频处理领域,计算量占比非常之高,效率仍然是重中之重。比如H.264的代码,最完善的应该是JM264,但在现实应用中,你绝对不会采用这个完善的方案,而是会采用对效率进行了大量优化的X264等编解码器。

Android getPixels图像处理时的内存分配

写本文之前,是因为网络上很少有对Android函数的清晰解释,因为我也不想重复造轮子。大部分对getPixels参数的解释都模糊不清。所以才决定科普一下,有了这篇文章。

首先,假设我们定义了mBitmap1,图片如下图所示,宽度和高度分别为wd = 100Pixel, ht = 100Pixel,

然后,我们需要对其像素进行处理,那就先要获取其全部的像素,getPixels不能对mBitmap原来的内存空间进行处理,必须开辟一块新的内存,当然取决于你所要处理的情况,你可以开辟适当的内存,可以小于原来图片大小,也可以大于原来图片大小。我这里为了说明问题,就开辟了一个2*wd * ht 的内存空间,注意这是一个连续的空间,本身并没有什么形状,我画成2*wd的宽度,仅仅是为了说明问题。如下图,

getPixels本身非常非常简单,就是一个拷贝的过程。

关键:拷贝的话,就涉及到2个因素,从哪里来,到哪里去?看一下函数定义

public void getPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height) {
    throw new RuntimeException("Stub!");
}

这里,x, y, width, height是属于从哪里来的参数,也就是我们控制怎样读取mBitmap1的参数;

offset, stride是到哪里去的参数,也就是控制如何放入到pixels[]中去的参数;

相信这里你已经大概明白了不少:offset是目标内存的起始地址的偏移量,stride是目标内存中隔多少个Pixels再写下一行;

这样后面的例子就更容易了解了。

例1

在这个例子里面,我们把源图片拷贝两次填充到目标图片中,目标图片的宽度我们设为2*wd,也就是200。原源图片中读取时,是一行一行读取的,也就是每次读取的是mBitmap1的宽度大小;

第一次读取,我们把偏移地址offset设为0,读取源图片中第一行得到的数据就会填充Pixels[0]~Pixels[wd-1];当读取到第二行后,往哪填充呢?这时就会找stride来决定,当看到stride=2*wd时,就会填充Pixels[2*wd+0]~Pixels[2*wd+wd-1],这个2*wd就是新图片的宽度;读完之后,新图片的左半部分就是源图片的全部内容。

第二次读取,我们把偏移地址offset设为wd,读取源图片中第一行得到的数据就会填充Pixels[wd]~Pixels[wd+wd-1];当读取到第二行后,同样会找stride来决定,当看到stride=2*wd时,就会填充Pixels[2*wd+wd]~Pixels[2*wd+wd+wd-1];同理,读完之后,新图片的右半部分就是源图片的全部内容。

源码如下所示,

mBitmap1.getPixels(pixels, 0, 2*wd, 0, 0, wd, ht);
mBitmap1.getPixels(pixels, wd, 2*wd, 0, 0, wd, ht);
mBitmap2 = Bitmap.createBitmap(pixels, 0, 2*wd, 2*wd, ht, Bitmap.Config.ARGB_8888);
mDstImg.setImageBitmap(mBitmap2);

效果如下所示

例2

现在我们看局部拷贝的方式。

目标地址偏移offset = (2*wd)*(ht/2)+wd/2,也就是在目标图片的1/2高度,再向右跑wd/2=50个像素,stride仍然是新图片的宽度2*wd,

这次源图中,我们只读取右下角的1/4,所以(x,y)的坐标就是(wd/2, ht/2),要读取的宽度是wd/2, 高度是ht/2。前面已经说过,源图中读取是每次一行读取的,因为这里读取的宽度是wd/2,所以这一行只读wd/2,剩下的就不会再读取了,也就是第一行只填充了Pixel[offset+0]~Pixel[offset+wd/2-1],

同理,

第2行读取的wd/2个像素填充Pixel[offset+stride+0]~Pixel[offset + stride + wd/2-1]

第3行读取的wd/2个像素填充Pixel[offset+2*stride+0]~Pixel[offset + 2*stride + wd/2-1]

offset = (2*wd)*(ht/2)+wd/2;
mBitmap1.getPixels(pixels, offset, 2*wd, wd/2, ht/2, wd/2, ht/2);
mBitmap3 = Bitmap.createBitmap(pixels, 0, 2*wd, 2*wd, ht, Bitmap.Config.ARGB_8888);
mDstImg.setImageBitmap(mBitmap3);

效果如下,

例3

还是上面的源码,我只是把stride由2*wd变成了2*wd-1,为了方便我写成stride-1比较好,因为stride正好是新图片的一行的宽度。

offset = (2*wd)*(ht/2)+wd/2;
mBitmap1.getPixels(pixels, offset, 2*wd-1, wd/2, ht/2, wd/2, ht/2);
mBitmap3 = Bitmap.createBitmap(pixels, 0, 2*wd, 2*wd, ht, Bitmap.Config.ARGB_8888);
mDstImg.setImageBitmap(mBitmap3);

知道有什么效果吗?

第1行填充:Pixel[offset+0]~Pixel[offset+wd/2-1]

第2行读取的wd/2个像素填充Pixel[offset+(stride-1)+0]~Pixel[offset +(stride-1) + wd/2-1]

第3行读取的wd/2个像素填充Pixel[offset+2*(stride-1)+0]~Pixel[offset + 2*(stride-1) + wd/2-1];

结果, 第2行填充不在第1行正下方,而是要左移1个像素,第3行左移2个像素,第n行就要左移n-1个像素,所以这个局部的图片是斜的,

到这里,相信你已经完全了解了getPixels。

项目演示源码地址:

https://download.csdn.net/download/tanmx219/10576699

转载请注明出处。

猜你喜欢

转载自blog.csdn.net/tanmx219/article/details/81328315