Android: 格式为RGB_565的bitmap问题

一般我们写代码的时候,创建的bitmap格式都为ARGB_8888, 包含alpha通道,并且可以获得最好的图片质量。但是有些时候,我们还是会需要使用到格式为RGB_565的bitmap, 以减少需要处理的数据量。

 

那么创建格式为RGB_565的bitmap有什么问题呢,先看下面一段代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>


struct BitmapFileHeader {
	uint16_t bfType;
	uint32_t bfSize;
	uint16_t bfReserved1;
	uint16_t bfReserved2;
	uint32_t bfOffBits;
}__attribute__((__packed__));

struct BitmapInfoHeader {
	uint32_t biSize;
	uint32_t biWidth;
	uint32_t biHeight;
	uint16_t biPlanes;
	uint16_t biBitCount;
	uint32_t biCompression;
	uint32_t biSizeImage;
	uint32_t biXPelsPerMeter;
	uint32_t biYPelsPerMeter;
	uint32_t biClrUsed;
	uint32_t biClrImportant;
};

void saveBitmap(const char *name, int width, int height, int bitCount, const unsigned char *pixels)
{
	struct BitmapFileHeader bfh;
	struct BitmapInfoHeader bih;
	FILE *fp;
	char buf[256];

	memset(&bfh, 0x00, sizeof(struct BitmapFileHeader));
	memset(&bih, 0x00, sizeof(struct BitmapInfoHeader));

	bfh.bfType = 0x4d42;
	bfh.bfSize = sizeof(bfh) + sizeof(bih) + ((width + 3) & ~3) * height * bitCount / 8;
	bfh.bfOffBits = sizeof(bfh) + sizeof(bih);

	bih.biSize = sizeof(bih);
	bih.biWidth = width;
	bih.biHeight = -height; // top-down image
	bih.biPlanes = 1;
	bih.biBitCount = 16;
	bih.biCompression = 0; //BI_RGB;

	fp = fopen(name, "w+");
	fwrite(&bfh, sizeof(bfh), 1, fp);
	fwrite(&bih, sizeof(bih), 1, fp);
	fwrite(pixels, ((width + 3) & ~3) * height * bitCount / 8, 1, fp);
	fclose(fp);
}

int main(int argc, const char *argv[])
{
	unsigned short color_table[] = {
		0x0000, 0x0020, 0x0041, 0x0441, 0x0462, 0x0482, 0x04a2, 0x08a3,
		0x08c3, 0x08e3, 0x0904, 0x0924, 0x0d25, 0x0d45, 0x0d65, 0x0d86,
		0x11a6, 0x11a6, 0x11c7, 0x11e7, 0x1208, 0x1608, 0x1628, 0x1649,
		0x1669, 0x1689, 0x1a8a, 0x1aaa, 0x1acb, 0x1aeb, 0x1eeb, 0x1f0c,
		0x1f2c, 0x1f4c, 0x1f6d, 0x236d, 0x238e, 0x23ae, 0x23ce, 0x27cf,
		0x27ef, 0x240f, 0x2430, 0x2450, 0x2851, 0x2871, 0x2891, 0x28b2,
		0x2cd2, 0x2cd2, 0x2cf3, 0x2d13, 0x2d34, 0x3134, 0x3154, 0x3175,
		0x3195, 0x31b5, 0x35b6, 0x35d6, 0x35f7, 0x3617, 0x3a17, 0x3a38,
		0x3a58, 0x3a78, 0x3a99, 0x3e99, 0x3eba, 0x3eda, 0x3efa, 0x42fb,
		0x431b, 0x433b, 0x435c, 0x437c, 0x477d, 0x479d, 0x47bd, 0x47de,
		0x4bfe, 0x4bfe, 0x481f, 0x483f, 0x4840, 0x4c40, 0x4c60, 0x4c81,
		0x4ca1, 0x50c1, 0x50c2, 0x50e2, 0x5103, 0x5123, 0x5523, 0x5544,
		0x5564, 0x5584, 0x55a5, 0x59a5, 0x59c6, 0x59e6, 0x5a06, 0x5e07,
		0x5e27, 0x5e47, 0x5e68, 0x5e88, 0x6289, 0x62a9, 0x62c9, 0x62ea,
		0x670a, 0x670a, 0x672b, 0x674b, 0x676c, 0x6b6c, 0x6b8c, 0x6bad,
		0x6bcd, 0x6fed, 0x6fee, 0x6c0e, 0x6c2f, 0x6c4f, 0x704f, 0x7070,
		0x7090, 0x70b0, 0x70d1, 0x74d1, 0x74f2, 0x7512, 0x7532, 0x7933,
		0x7953, 0x7973, 0x7994, 0x79b4, 0x7db5, 0x7dd5, 0x7df5, 0x7e16,
		0x0236, 0x0236, 0x0257, 0x0277, 0x0298, 0x0698, 0x06b8, 0x06d9,
		0x06f9, 0x0b19, 0x0b1a, 0x0b3a, 0x0b5b, 0x0b7b, 0x0f7b, 0x0f9c,
		0x0fbc, 0x0fdc, 0x0ffd, 0x13fd, 0x101e, 0x103e, 0x105e, 0x145f,
		0x147f, 0x149f, 0x14a0, 0x14c0, 0x18c1, 0x18e1, 0x1901, 0x1922,
		0x1d42, 0x1d42, 0x1d63, 0x1d83, 0x1da4, 0x21a4, 0x21c4, 0x21e5,
		0x2205, 0x2625, 0x2626, 0x2646, 0x2667, 0x2687, 0x2a87, 0x2aa8,
		0x2ac8, 0x2ae8, 0x2f09, 0x2f09, 0x2f2a, 0x2f4a, 0x2f6a, 0x336b,
		0x338b, 0x33ab, 0x33cc, 0x33ec, 0x37ed, 0x340d, 0x342d, 0x344e,
		0x386e, 0x386e, 0x388f, 0x38af, 0x38d0, 0x3cd0, 0x3cf0, 0x3d11,
		0x3d31, 0x4151, 0x4152, 0x4172, 0x4193, 0x41b3, 0x45b3, 0x45d4,
		0x45f4, 0x4614, 0x4a35, 0x4a35, 0x4a56, 0x4a76, 0x4a96, 0x4e97,
		0x4eb7, 0x4ed7, 0x4ef8, 0x4f18, 0x5319, 0x5339, 0x5359, 0x537a,
		0x579a, 0x579a, 0x57bb, 0x57db, 0x57fc, 0x5bfc, 0x581c, 0x583d,
		0x585d, 0x5c7d, 0x5c7e, 0x5c9e, 0x5cbf, 0x5cdf, 0x60df, 0x60e0,
	};
	unsigned short *pixels;
	int i, j;
	int W = 256*3, H = 256*2;

	// create a (256*3)x(256*2)x2 bitmap
	pixels = (unsigned short *)malloc(W * H * 2);

	for (i = 0; i < H; i++) {
		for (j = 0; j < 256; j++) {
			*(pixels + W * (H - 1 - i) + j * 3 + 0) = color_table[j];
			*(pixels + W * (H - 1 - i) + j * 3 + 1) = color_table[j];
			*(pixels + W * (H - 1 - i) + j * 3 + 2) = color_table[j];
		}
	}

	saveBitmap("bitmap_rgb565.bmp", W, H, 16, (unsigned char *)pixels);

	free(pixels);

	return 0;
}

这段代码是用来创建一个名为bitmap_rgb565.bmp的图片,图片的宽度为256*3个pixel,高度为256*2个pixel。编译、运行上面的代码可以得到如下图片:

$ gcc -o bitmap_rgb565 bitmap_rgb565.c
$ ./bitmap_rgb565

 

2016_0217_bitmap_rgb565

图片本身没什么特别。

由于有了libgraphics这个库,我们可以很方便地在native代码(JNI)中得到bitmap pixel所对应的内存地址。我们可以接对bitmap中的pixel进行修改。这在java代码中基本上不太可能,虽然Bitmap也提供了如copyPixelsFromBuffer()、copyPixelsToBuffer()、getPixel()、setPixel()等这些方法,但是性能跟不上。下面,我们看一下如何在native层对bitmap的像素点进行操作:

首先在java代码中创建一个格式为RGB_565的Bitmap对象, 再将这个对象通过JNI接口(update())传递给native层:

public class CubicActivity extends Activity {
    private static final String TAG = "Cubic";

    private static class CubicView extends View {
        private Bitmap mBitmap;
        private int W = 256 * 3;
        private int H = 256 * 2;

        public CubicView(Context context) {
            super(context);

            mBitmap = Bitmap.createBitmap(W, H, Bitmap.Config.RGB_565);
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);

            post(new Runnable() {
                @Override
                public void run() {
                    update(mBitmap);
                    invalidate();
                }
            });
        }

        @Override
        public void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawBitmap(mBitmap, 0, 0, null);
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new CubicView(this));
    }

    // ----------------------------------------------------
    static {
        System.loadLibrary("cubic-jni");
    }

    private static native void update(Bitmap bitmap);

在native层,我们可以通过AndroidBitmap_getInfo()得到bitmap的相关信息,如宽度,高度等。再调用AndroidBitmap_lockPixels()得到bitmap像素点所在的内存地址,操用完成之后再调用AndroidBitmap_unlockPixels()进行提交:

extern "C" void Java_com_brobwind_cubic_CubicActivity_update(JNIEnv *env, jobject, jobject jbitmap)
{
    // Get the info.
    AndroidBitmapInfo info;
    int result = AndroidBitmap_getInfo(env, jbitmap, &info);
    if (result < 0) {
        throwIllegalStateException(env, (char*)"Cannot get bitmap info");
        return;
    }

    // Lock the pixels.
    void* ptr;
    result = AndroidBitmap_lockPixels(env, jbitmap, &ptr);
    if (result < 0) {
        throwIllegalStateException(env, (char*)"Cannot lock bitmap pixels");
        return;
    }

    unsigned short *pixels = (unsigned short *)ptr;
    unsigned short color_table[] = {
        0x0000, 0x0020, 0x0041, 0x0441, 0x0462, 0x0482, 0x04a2, 0x08a3,
        ...
    };

    for (int i = 0; i < (int)info.height; i++) {
        for (int j = 0; j < (int)info.width / 3; j++) {
            *(pixels + info.width * (info.height - 1 - i) + j * 3 + 0) = color_table[j];
            *(pixels + info.width * (info.height - 1 - i) + j * 3 + 1) = color_table[j];
            *(pixels + info.width * (info.height - 1 - i) + j * 3 + 2) = color_table[j];
        }
    }

    // Unlock the pixels.
    result = AndroidBitmap_unlockPixels(env, jbitmap);
    if (result < 0) {
        throwIllegalStateException(env, (char*)"Cannot unlock bitmap pixels");
    }
}

仔细查看的话,可以发现这些代码实际上是要在应用程序的窗口中直接绘制之前的那张位图。实际的显示效果又是怎么样呢:

2016_02_17_Screenshot_2016-02-17-20-54-53

为什么会这样呢?

在bitmap文件或者是windows中的CreateDIBSection(BI_RGB, 16)创建的位图,pixel中r, g, b排列为:

f e d c b a 9 8 7 6 5 4 3 2 1 0
  --------- --------- ---------
      r         g         b

而Android中的RGB_565, pixel中的r, g, b排列为:

f e d c b a 9 8 7 6 5 4 3 2 1 0
--------- ----------- ---------
    r          g          b

都是16位色,但是不能直接copy。如果想直接保存这个位图,可以指定compression为BI_BITFIELDS,指明alpha, red, green, blue的mask。

            unsigned short B = color_table[j];
            unsigned short B_r = (B >> 10) & 0x1f;
            unsigned short B_g = (B >>  5) & 0x1f;
            unsigned short B_b = (B >>  0) & 0x1f;
            B = B_r << 11 | B_g << 6 | B_b;

            *(pixels + info.width * (info.height - 1 - i) + j * 3 + 0) = B;
            *(pixels + info.width * (info.height - 1 - i) + j * 3 + 1) = B;
            *(pixels + info.width * (info.height - 1 - i) + j * 3 + 2) = B;

附工程文件:2016_02_17_HelloCubic.v1.tar.gz

 

相关文档:

  1. https://en.wikipedia.org/wiki/BMP_file_format

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注