将 Java 位图打包到 ByteBuffer 中 - 字节顺序与像素格式和字节序不匹配(ARM)

Posted

技术标签:

【中文标题】将 Java 位图打包到 ByteBuffer 中 - 字节顺序与像素格式和字节序不匹配(ARM)【英文标题】:Packing Java bitmap into ByteBuffer - byte order doesn't match pixel format and endianness (ARM) 【发布时间】:2019-09-10 09:45:52 【问题描述】:

我对 ByteBuffer 中 Bitmap 像素的内部表示有点困惑(在 ARM/little endian 上测试):

1) 在 Java 层中,我创建了一个 ARGB 位图并用 0xff112233 颜色填充它:

Bitmap sampleBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(sampleBitmap);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);

paint.setColor(Color.rgb(0x11,0x22, 0x33));
canvas.drawRect(0,0, sampleBitmap.getWidth(), sampleBitmap.getHeight(), paint);

为了测试,sampleBitmap.getPixel(0,0) 确实返回了匹配 ARGB 像素格式的 0xff112233。

2) 位图在传递到native层之前被打包到直接的ByteBuffer中:

 final int byteSize = sampleBitmap.getAllocationByteCount();
 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(byteSize);
 //byteBuffer.order(ByteOrder.LITTLE_ENDIAN);// See below
 sampleBitmap.copyPixelsToBuffer(byteBuffer);

为了测试,不管缓冲区的顺序设置如何,在调试器中我看到的字节布局与 ARGB 不太匹配,但更像是大端 RGBA(或小端 ABGR!?)

  byteBuffer.rewind();
  final byte [] out = new byte[4];
  byteBuffer.get(out, 0, out.length);
out = byte[4]@12852 
 0 = (0x11)
 1 = (0x22)
 2 = (0x33)
 3 = (0xFF)

现在,我将此位图传递到必须提取像素的本机层,我希望 Bitmap.Config.ARGB_8888 被表示,具体取决于缓冲区的字节顺序:

a) byteBuffer.order(ByteOrder.LITTLE_ENDIAN):

out = byte[4]@12852 
 0 = (0x33)
 1 = (0x22)
 2 = (0x11)
 3 = (0xFF)

b) byteBuffer.order(ByteOrder.BIG_ENDIAN):

out = byte[4]@12852 
 0 = (0xFF)
 1 = (0x11)
 2 = (0x22)
 3 = (0x33)

我可以根据上面的输出使提取像素的代码工作,但我不喜欢它,因为我无法解释我希望有人会做的行为:)

谢谢!

【问题讨论】:

我不确定我是否理解。 little-endian 布局在我看来完全正确。你期待什么? 我期待 a) 或 b)。我所拥有的看起来不像任何 ARGB 布局,而是 RGBA 的一些变体。 好的。 Bitmap.Config.ARGB_8888 的文档说 “使用此公式打包成 32 位:int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff); 是的,这显然是个问题:***.com/questions/56832572/… 另外,可以通过 bitmap.getPixel() 验证,它按预期返回 0xff112233 (ARGB)。 【参考方案1】:

让我们看一下实现。 getPixelcopyPixelsToBuffer 都只是调用它们的本地对应物。 Bitmap_getPixels 指定输出格式:

SkImageInfo dstInfo = SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType, sRGB);
bitmap.readPixels(dstInfo, &dst, dstInfo.minRowBytes(), x, y);

它有效地要求位图将像素值转换为BGRA_8888(由于本机和java字节序不同,它变为ARGB)。

Bitmap_copyPixelsToBuffer 反过来只是复制原始数据:

memcpy(abp.pointer(), src, bitmap.computeByteSize());

并且没有任何转换。它基本上以与存储数据相同的格式返回数据。让我们看看这个内部格式是什么。

Bitmap_creator 用于创建新的位图,并从调用传递的配置中获取格式

SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle);

查看legacyBitmapConfigToColorType 实现,ARGB_8888(索引为 5)变为kN32_SkColorType

kN32_SkColorType 来自skia 库,所以查看the definitions 找到评论

kN32_SkColorType is an alias for whichever 32bit ARGB format is the 
"native" form for skia's blitters. Use this if you don't have a swizzle 
preference for 32bit pixels.

以下是定义:

#if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
    kN32_SkColorType = kBGRA_8888_SkColorType,
#elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
    kN32_SkColorType = kRGBA_8888_SkColorType,

SK_PMCOLOR_BYTE_ORDER 被定义为here,它说SK_PMCOLOR_BYTE_ORDER(R,G,B,A) will be true on a little endian machine,这就是我们的例子。所以这意味着位图在内部以kRGBA_8888_SkColorType格式存储。

【讨论】:

感谢您的彻底调查。它解释了这种行为,但最好在文档中的某处提到使用位图缓冲区时的内部字节顺序可能不同。 我完全同意,这里的文档不完整。它说The content of the bitmap is copied into the buffer as-is.,但没有说它实际上是什么。

以上是关于将 Java 位图打包到 ByteBuffer 中 - 字节顺序与像素格式和字节序不匹配(ARM)的主要内容,如果未能解决你的问题,请参考以下文章

将Java位图转换为字节数组

java将InputStream呈现为ByteBuffer而不将整个内容加载到数组中

如何使用 JNI 将 Java ByteBuffer 发送到 C?

Java Bytebuffer 只能顺序读取?

Camerax 图像分析:将图像转换为 bytearray 或 ByteBuffer

带有移位字节的新 ByteBuffer (Java)?