WebP图片宽高存储字节位置咨询及Gradle插件解析方案问询
WebP图片宽高的字节位置及Gradle解析实现
WebP文件结构与宽高存储位置
WebP基于RIFF容器格式,先确认文件头部:前12字节固定为RIFF(0-3字节)+ 文件大小(4-7字节,小端序)+ WEBP(8-11字节)。之后的块(Chunk)决定了宽高的存储位置,分三种类型:
1. 有损WebP(VP8块)
- 找到标识为
VP8(注意末尾带空格,12-15字节)的块 - 跳过4字节的块大小(16-19字节)
- 从20字节开始的4字节是VP8帧头核心段:
- 高度:取第20字节的低2位(作为高2位),拼接第21字节的完整8位,公式:
((byte20 & 0x3) << 8) | (byte21 & 0xFF) - 宽度:取第22字节的低2位(作为高2位),拼接第23字节的完整8位,公式:
((byte22 & 0x3) << 8) | (byte23 & 0xFF)
- 高度:取第20字节的低2位(作为高2位),拼接第21字节的完整8位,公式:
2. 无损WebP(VP8L块)
- 找到标识为
VP8L(12-15字节)的块 - 跳过4字节的块大小(16-19字节)
- 从20字节开始的VP8L头部中,宽高各占14位(小端位存储),常规场景下可简化解析:
- 宽度:第20字节的低6位 + 第21字节的8位,公式:
((byte20 & 0x3F) << 8) | (byte21 & 0xFF) - 高度:第22字节的低6位 + 第23字节的8位,公式:
((byte22 & 0x3F) << 8) | (byte23 & 0xFF)
(如果宽高超过14位最大值,需要继续读取后续字节补充位,不过绝大多数图片不会触发这种情况)
- 宽度:第20字节的低6位 + 第21字节的8位,公式:
3. 扩展WebP(VP8X块,带透明/动画)
- 找到标识为
VP8X(12-15字节)的块 - 跳过4字节的块大小(16-19字节)
- 从20字节开始的7字节中,宽高存储在21-23(宽度)、24-26(高度)字节:
- 宽度:21-23字节按小端序拼接成24位整数后加1,公式:
((byte21 & 0xFF) | ((byte22 & 0xFF) << 8) | ((byte23 & 0xFF) << 16)) + 1 - 高度:24-26字节按小端序拼接成24位整数后加1,公式:
((byte24 & 0xFF) | ((byte25 & 0xFF) << 8) | ((byte26 & 0xFF) << 16)) + 1
- 宽度:21-23字节按小端序拼接成24位整数后加1,公式:
Gradle插件中解析宽高的实现思路
在Gradle插件里,通过读取文件字节流定位对应位置即可,以下是Groovy实现的核心代码片段:
def getWebpDimension(File webpFile) { FileInputStream fis = null try { fis = new FileInputStream(webpFile) // 验证WebP文件头 byte[] riffHeader = new byte[12] if (fis.read(riffHeader) != 12 || !new String(riffHeader[8..11]).equals("WEBP")) { return null } byte[] chunkId = new byte[4] byte[] chunkSizeBytes = new byte[4] while (fis.read(chunkId) == 4) { fis.read(chunkSizeBytes) int chunkSize = ByteBuffer.wrap(chunkSizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt() String chunkName = new String(chunkId) switch (chunkName) { case "VP8 ": // 解析有损WebP宽高 byte[] vp8FrameHeader = new byte[4] fis.read(vp8FrameHeader) int height = ((vp8FrameHeader[0] & 0x3) << 8) | (vp8FrameHeader[1] & 0xFF) int width = ((vp8FrameHeader[2] & 0x3) << 8) | (vp8FrameHeader[3] & 0xFF) return [width: width, height: height] case "VP8L": // 解析无损WebP宽高 byte[] vp8lHeader = new byte[4] fis.read(vp8lHeader) int wpWidth = ((vp8lHeader[0] & 0x3F) << 8) | (vp8lHeader[1] & 0xFF) int wpHeight = ((vp8lHeader[2] & 0x3F) << 8) | (vp8lHeader[3] & 0xFF) return [width: wpWidth, height: wpHeight] case "VP8X": // 解析扩展WebP宽高 byte[] vp8xHeader = new byte[7] fis.read(vp8xHeader) int xWidth = ((vp8xHeader[1] & 0xFF) | ((vp8xHeader[2] & 0xFF) << 8) | ((vp8xHeader[3] & 0xFF) << 16)) + 1 int xHeight = ((vp8xHeader[4] & 0xFF) | ((vp8xHeader[5] & 0xFF) << 8) | ((vp8xHeader[6] & 0xFF) << 16)) + 1 return [width: xWidth, height: xHeight] default: // 跳过无关块 fis.skip(chunkSize) } } } catch (IOException e) { e.printStackTrace() } finally { fis?.close() } return null }
注意事项
- Java/Groovy中的
byte是有符号类型,解析时必须用& 0xFF转为无符号整数,避免位运算错误 - 需处理文件读取的异常情况(如文件不存在、读取中断等)
- 若要兼容极端大尺寸的无损WebP,需要补充后续字节的位解析逻辑
内容的提问来源于stack exchange,提问作者ke le
相关产品推荐
相关产品推荐

