在Kotlin中解析webp文件头获取其高度和宽度,但得到意想不到的结果

问题描述 投票:0回答:2

我正在尝试根据扩展文件格式的WebP容器规范读取WebP图像标题。

fun get24bit(data: ByteArray, index: Int): Int {
    return ((data[0 + index].toInt()) or (data[1 + index].toInt() shl 8) or (data[2 + index].toInt() shl 16))
}

fun get32bit(data: ByteArray, index: Int): Int {
    return get24bit(data, index) or (data[3 + index].toInt() shl 24)
}

// data -> File(fileName).readBytes() for testing purpose
fun webpExtract(data: ByteArray) {
    println(String(data.copyOfRange(0, 4)))
    println("Size: ${get32bit(data, 4)}")
    println(String(data.copyOfRange(8, 12)))
    println(String(data.copyOfRange(12, 16)))
    // 16, 17, 18, 19 reserved

    val width = 1 + get24bit(data, 20)
    val height = 1 + get24bit(data, 23)

    println("Width: $width, Height: $height")
}

输出为:

RIFF
Size: -52
WEBP
VP8X
Width: 17, Height: 32513

字符串输出没问题,但大小变为负值,宽度和高度错误,即它们应该分别为 128 和 128(对于我使用的测试图像)。

代码有问题吗?我无法弄清楚问题出在哪里。

我还在 github 验证了实际的 C++ 实现。我的代码执行相同的位移,但结果不正确。据我所知,左移与无符号和有符号右移没有任何关系?

kotlin bitwise-operators bit-shift webp
2个回答
2
投票
不知道

Spec不完整或其他什么,我记录了字节值并以某种方式找到了模式。发现尺寸为 24-26 和 27-29 索引。

val width = 1 + (get24bit(data, 24)) val height = 1 + (get24bit(data, 27))
这个就可以了!希望只要文档未更新,注意这一点会有所帮助。


2
投票
接受的答案仅适用于某些 WebP 文件(扩展格式 VP8X),但还有其他两种格式(有损 VP8 和无损 VP8L)不适用于该答案。

这 3 种格式有不同的获取尺寸的方法。

fun getWebPDimensions(imgFile: File) { val stream = FileInputStream(imgFile) val data = stream.readNBytes(30) // All formats consist of a file header (12 bytes) and a ChunkHeader (8 bytes) // The first four ChunkHeader bytes contain the 4 characters of the format (12 to 15): val imageFormat = String(Arrays.copyOfRange(data, 12, 16)) // exclusive range val width: Int val height: Int when(imageFormat) { "VP8 " -> { // last character is a space // Simple File Format (Lossy) // The data is in the VP8 specification and the decoding guide explains how to get the dimensions: https://datatracker.ietf.org/doc/html/rfc6386#section-19.1 // The format consists of the frame_tag (3 bytes), start code (3 bytes), horizontal_size_code (2 bytes) and vertical_size_code (2 bytes) // The size is 14 bits, use a mask to remove the last two digits width = get16bit(data, 26) and 0x3FFF height = get16bit(data, 28) and 0x3FFF } "VP8X" -> { // Extended File Format, size position specified here: https://developers.google.com/speed/webp/docs/riff_container#extended_file_format // The width starts 4 bytes after the ChunkHeader with a size of 3 bytes, the height comes after. width = 1 + (get24bit(data, 24)) height = 1 + (get24bit(data, 27)) } "VP8L" -> { // Simple File Format (Lossless), specification here: https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#3_riff_header // The format consists of a signature (1 byte), 14 bit width (2 bytes) and 14 bit height (2 bytes) // The width and height are in consecutive bits val firstBytes = get16bit(data, 21) width = 1 + (firstBytes and 0x3FFF) val lastTwoDigits = (firstBytes and 0xC000) shr 14 // the last 2 bits correspond to the first 2 bits of the height // Extract the remaining 12 bits and shift them to add space for the two digits height = 1 + ((get16bit(data, 23) and 0xFFF shl 2) or lastTwoDigits) } } } private fun get16bit(data: ByteArray, index: Int): Int { // The mask (0xFF) converts the byte from signed (this is how Java originally reads the byte) to unsigned return data[index].toInt() and 0xFF or (data[index + 1].toInt() and 0xFF shl 8) } private fun get24bit(data: ByteArray, index: Int): Int { return get16bit(data, index) or (data[index + 2].toInt() and 0xFF shl 16) }
    
© www.soinside.com 2019 - 2024. All rights reserved.