【Android】二进制图片和Bitmap的getPixel方法解析

Android中Bitmap的getPixel方法解析

第一次写博客,一直想动笔,但是感觉想写的东西网上都有很详细的了。。。今天终于下定决心,写第一篇博客。感觉博客这个东西,别人的和自己的是不一样的,即使同样的问题,也还是要自己记录下来印象更深刻。但是写博客,切勿复制!!!网上一模一样复制过来的东西太多了。


  • 计算机识别的图片
  • 图片的颜色通道
  • getPixel方法的返回值

计算机识别的图片

最近想了解一下图像相关的东西,于是从图片是怎样保存在计算机上着手,简单的研究了一下。

大家都知道,计算机只能识别‘0’和‘1’。图片也一样,通过二进制保存在计算机中。图片一般分为32位(ARGB_8888)和16位(ARGB_4444/ARGB565)。前面代表颜色通道,后面代表每个颜色所占的字节。

如”ARGB_8888”有4个颜色通道,每个色值占8个bit,则ARGB_8888的图片,一像素占用 4(通道)*8(bit)=32bit内存。我们可以用代码验证一下。

Bitmap bitmap1 
    = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
Bitmap bitmap2 
    = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
Log.i(TAG,"ARGB_8888:"+bitmap1.getByteCount());
Log.i(TAG,"ARGB_565:"+bitmap2.getByteCount());

1byte=8bit,打印两张图片所占的字节,得到的值分别为4和2。对应着32位和16位。需要注意的是,如果我们打印ARGB_4444所占的字节,也会得到4byte,这是因为Android已经废弃了ARGB_4444,采用质量更好的ARGB_8888来代替它。

createBitmap部分源码:

if (config != null) {
    switch (config) {
        case ARGB_4444:
        case ARGB_8888:
        default:
            newConfig = Config.ARGB_8888;
            break;
    }
}

图片的颜色通道

知道了图片所占的内存,那么每个颜色通道占8bit是什么意思呢?我们一般表示颜色都是用16进制的ARGB,如一个不透明的红色为FFFF0000,不过计算机只能识别二进制,所以,不透明的红色由16进制转换成二进制为
1111 1111 1111 1111 0000 0000 0000 0000

相信大家已经可以看出来了,我们常用的16进制颜色,转换成二进制后,每个颜色的最大值为一个占用8bit的二进制数字。这就是每个颜色通道占8bit的意思。即每个颜色用一个8位的二进制表示。

由此我们也可以知道,颜色位数越大,所能呈现的色彩就越多,图片也就更加鲜艳。所以,目前在Android中,ARGB_8888格式的bitmap颜色是最好的。

注意,有的同学用这种方式打印图片的二进制

Bitmap bitmap 
    = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
ByteArrayOutputStream byteArrayOutputStream
    = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG
                , 100
                , byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();

这种方式用于传递图片之类的还可以,但是如果要看图片二进制,你会发现打印出的byte数组比你自己create的原始图片大很多,因为这毕竟是个压缩的方法,调用后会有一些压缩的信息保存在里面,所以得到的byte数组不完全是图片本身的。

具体能打印图片真实二进制的方法我也没找到,如果有同学知道的话希望请教一下,大家互相交流哈。


getPixel方法的返回值

知道了这些后,我们开始研究一下getPixel。看方法名不难看出,这应该是返回图片某个像素点的颜色。看源码的注释也确实是这样。但是我们发现,getPixel方法返回的是一个int型的值。平时用颜色的时候int型好说,都是int型的,但是返回int型咋用??有时候还有负数??

这里写图片描述

其实Color类中有alpha、red、green、blue方法,虽然不常用,但是可以提取中int型颜色中对应的ARGB色值。

Bitmap bitmap
    = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setPixel(0, 0, Color.RED);
int pixel = bitmap.getPixel(0, 0);
// pixel = -65536;
int a = Color.alpha(pixel);
int r = Color.red(pixel);
int g = Color.green(pixel);
int b = Color.blue(pixel);
//得到a : 255 , r : 255 , g : 0 , b : 0;

不过光会用不行,这一串int型到底是怎么表示颜色的,我们还是要看看的。

知道返回的是颜色,并且还有负数,肯定是和二进制有关。我们得到的值是-65536,转换成32位的二进制。

负数十进制转二进制等于对应二进制的绝对值的反码加1。转换成32位,不足的前面补0。

取绝对值
|-65536| = 65536。
转换成二进制:
0000 0000 0000 0001 0000 0000 0000 0000
取反:
1111 1111 1111 1110 1111 1111 1111 1111
+1:
1111 1111 1111 1111 0000 0000 0000 0000

因为我们用的颜色是红色,属于比较规则的颜色,转换成二进制后,我们一下就能看出来,这32位二进制不就是之前说的对应的颜色通道么。1111 1111 正好对应16进制的 FF

接下来就简单了,我们只要把不同位的二进制数字每8位一组提取出来就可以了。

首先保留最低的8位,我们只要把原码与
0000 0000 0000 0000 0000 0000 1111 1111进行‘&(与)’运算即可保留最低的8位。即与十进制的255或者16进制的ff等进行&运算
1111 1111 1111 1111 0000 0000 0000 0000 & 0xff

为了方便观看,我这里还使用比较长的二进制。
1111 1111 1111 1111 0000 0000 0000 0000
&
0000 0000 0000 0000 0000 0000 1111 1111
得到
0000 0000 0000 0000 0000 0000 0000 0000
则最低位也就是blue的值为0,符合我们给定的值。

同理,我们把这8位抛去,只保留9-16位也就是green的色值。
1111 1111 1111 1111 0000 0000 0000 0000
&
0000 0000 0000 0000 1111 1111 0000 0000
得到
0000 0000 0000 0000 0000 0000 0000 0000

看起来green的值是0,好像是对了,但是,如果green不是0呢?我们来用red的值验证一下。
1111 1111 1111 1111 0000 0000 0000 0000
&
0000 0000 1111 1111 0000 0000 0000 0000
得到
0000 0000 1111 1111 0000 0000 0000 0000
看起来好像也没什么问题,不过,转换成10进制是多少呢?算出来一看16711680,这都几千万了。肯定是哪出错了。

我们再看一下,这里的1111 1111虽然是255没问题,但是这些1不是从起始位开始的。我们还需要把它转换到起始位。这好说,进行>>右移操作就可以了。
green的话是0,就不写了,red的值应为
0000 0000 1111 1111 0000 0000 0000 0000
右移 >>16 位得到
0000 0000 1111 1111

这就是我们需要的red的值了。所以,getPixel方法的返回值也可以通过如下方式转换成色值:

Bitmap bitmap 
    = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setPixel(0, 0, Color.RED);
int pixel = bitmap.getPixel(0, 0);
int a = (pixel & 0xff000000) >> 24;
int r = (pixel & 0xff0000) >> 16;
int g = (pixel & 0xff00) >> 8;
int b = (pixel & 0xff);
/*
//也可以写成
int a = pixel >> 24 & 0xff;
int r = pixel >> 16 & 0xff;
int g = pixel >> 8 & 0xff;
int b = pixel & 0xff;
*/
//同样得到a : 255 , r : 255 , g : 0 , b : 0;

好了,就到这里了,回头一看感觉好像有点乱。以后遇到问题还是要多记录,有什么不足的地方,大佬们多提意见吧!!!感激不尽!!!

**位图(Bitmap)** 是计算机图形学中用于表示图像的一种基础数据结构,其核心思想是通过**二进制位(bit)的排列**来记录每个像素的颜色信息。以下是位图的详细解析,涵盖其定义、存储原理、应用场景及与矢量图的区别: --- ### **1. 位图的定义与核心原理** #### **(1) 基本概念** - **位图**(又称**栅格图像**)将图像划分为由**像素(Pixel)**组成的网格,每个像素的颜色由**二进制位**表示。 - **最小单位**:1个像素通常占用**1位(黑白)**到**32位(RGBA透明通道)**不等,取决于颜色深度。 #### **(2) 存储结构** - **二维数组模型**: 位图可视为一个二维数组,每个元素存储一个像素的颜色值。例如: ```java // 伪代码:3x3的RGB位图 int[][][] bitmap = { {{255,0,0}, {0,255,0}, {0,0,255}}, // 第一行:红、绿、蓝 {{255,255,0}, {255,0,255}, {0,255,255}}, {{255,255,255}, {128,128,128}, {0,0,0}} }; ``` - **实际存储**: 在内存或文件中,位图数据通常按**行优先**或**列优先**顺序压缩存储(如BMP文件的`BITMAPINFOHEADER`结构)。 --- ### **2. 位图的关键属性** #### **(1) 分辨率** - **定义**:图像的像素数量,如`1920x1080`表示宽1920像素、高1080像素。 - **影响**:分辨率越高,图像越清晰,但文件大小内存占用也越大。 #### **(2) 颜色深度** - **常见格式**: - **1位**:黑白(如传真) - **8位**:灰度(256级)或索引颜色(调色板) - **24位**:真彩色(RGB各8位,共1677万色) - **32位**:带透明通道(RGBA) #### **(3) 压缩方式** - **无损压缩**: 保留所有像素数据(如PNG、BMP)。 - **有损压缩**: 通过丢弃人眼不敏感的信息减少文件大小(如JPEG)。 --- ### **3. 位图在Android中的应用** #### **(1) 核心类:`Bitmap`** - **作用**: Android提供的`android.graphics.Bitmap`类封装了位图数据,支持加载、操作绘制。 - **创建方式**: ```java // 从资源文件创建 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image); // 从字节数组创建 byte[] buffer = ...; Bitmap bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length); // 动态创建空白位图 Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); ``` #### **(2) 内存管理** - **堆内存分配**: `Bitmap`的像素数据存储在**Java堆**或**Native堆**(Android 8.0+默认),大图可能导致OOM。 - **回收机制**: 必须调用`bitmap.recycle()`手动释放内存,或依赖GC(不推荐)。 #### **(3) 常见操作** ```java // 缩放位图 Bitmap scaledBitmap = Bitmap.createScaledBitmap(original, 200, 200, true); // 裁剪位图 Bitmap croppedBitmap = Bitmap.createBitmap(original, 10, 10, 50, 50); // 像素级操作 int pixel = bitmap.getPixel(x, y); bitmap.setPixel(x, y, Color.RED); ``` --- ### **4. 位图 vs 矢量图** | **特性** | **位图** | **矢量图** | |----------------|-----------------------------------|---------------------------------| | **存储方式** | 像素数组 | 数学公式(路径、曲线) | | **缩放效果** | 放大后失真(马赛克) | 无限缩放不失真 | | **文件大小** | 与分辨率正相关 | 通常较小(仅存储几何信息) | | **适用场景** | 照片、复杂纹理 | 图标、Logo、简单图形 | | **Android类** | `Bitmap` | `VectorDrawable` | --- ### **5. 位图的文件格式** #### **(1) 常见格式** - **BMP**:无压缩,Windows原生格式。 - **PNG**:无损压缩,支持透明度。 - **JPEG**:有损压缩,适合照片。 - **WebP**:Google推出的高效格式(支持有损/无损)。 #### **(2) Android中的格式选择** ```java // 根据需求选择配置(影响内存占用) Bitmap.Config config = Bitmap.Config.ARGB_8888; // 高质量(32位) Bitmap.Config config = Bitmap.Config.RGB_565; // 低内存(16位,无透明) Bitmap bitmap = Bitmap.createBitmap(width, height, config); ``` --- ### **6. 位图的性能优化** #### **(1) 内存优化** - **采样率加载**: ```java BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; // 缩小为1/2 Bitmap bitmap = BitmapFactory.decodeFile("/path/to/image", options); ``` - **复用`Bitmap`**: 通过`inBitmap`参数复用已有位图的内存(需相同尺寸配置)。 #### **(2) 绘制优化** - **硬件加速**: 在`View`中启用`setLayerType(LAYER_TYPE_HARDWARE, null)`,利用GPU加速绘制。 - **避免频繁操作**: 批量处理像素修改(如使用`RenderScript`或`OpenGL`)。 --- ### **7. 源码中的位图处理(以Android为例)** #### **(1) `Bitmap`的Native层实现** - **`SkBitmap`**: Android的`Bitmap`底层依赖Skia图形库的`SkBitmap`类,管理像素数据的存储访问。 - **`GraphicBuffer`**: Android 8.0+将`Bitmap`的像素数据存储在`GraphicBuffer`中,减少Java堆内存占用。 #### **(2) 关键方法** ```cpp // frameworks/base/graphics/java/android/graphics/Bitmap.cpp static void Bitmap_recycle(JNIEnv* env, jobject thiz) { sk_sp<SkBitmap> bitmap = getBitmap(env, thiz); if (bitmap) { bitmap->freePixels(); // 释放Native内存 } } ``` --- ### **
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值