常见图片格式之二进制格式分析

您所在的位置:网站首页 嗨吃家是什么东西 常见图片格式之二进制格式分析

常见图片格式之二进制格式分析

2023-11-26 11:22| 来源: 网络整理| 查看: 265

我正在参加「掘金·启航计划」

分享背景

我们经常使用图片来表达信息,图片已然成为一种沟通方式。图片可以表达信息,我们也需要知道图片本身是如何被表达出来的。鲁迅说过知其然不如知其所以然,本文将就图片的类型和常见图片格式的二进制进行解析,希望大家看完会对图片有一个更系统的认识。

文中的源码示例,使用Dart语音编写,不过不了解Flutter的同学,也不影响阅读。

图片分类 光栅图(栅格图)

就是最小单位由像素构成的图,只有点的信息,缩放时会失真。JPG、PNG,webp等都属于此类。

为了更好说明,写了一个Demo,来更加形象的说明什么是栅格图。 思路很简单,就是使用image库来获取图片的宽高和每一个像素点的色值,然后使用Column,Row,ColoredBox三个组件显示出来。

附源码

import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image/image.dart' as img; void main() { runApp(const App()); } class App extends StatelessWidget { const App({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('栅格图片'), ), body: HomePage(), )); } } class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { img.Image? image; @override void initState() { super.initState(); loadImageFromAssets('assets/image/leaf_152.JPG').then((value) { setState(() { image = value; }); }); } //读取 assets 中的图片 Future loadImageFromAssets(String path) async { ByteData data = await rootBundle.load(path); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); return img.decodeImage(bytes); } @override Widget build(BuildContext context) { if (image != null) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('76 * 76'), Container( width: 76, height: 76, decoration: BoxDecoration( border: Border.all(color: Colors.amber, width: 1)), child: GridXYLayout( n: image!.width, m: image!.height, image: image!, ), ), Text('400 * 400'), Container( width: 400, height: 400, decoration: BoxDecoration( border: Border.all(color: Colors.amber, width: 1)), child: GridXYLayout( n: image!.width, m: image!.height, image: image!, ), ), ], ); } return const SizedBox.shrink(); } } class GridXYLayout extends StatelessWidget { img.Image image; // 行数 final int n; // 列数 final int m; GridXYLayout({Key? key, this.n = 3, this.m = 3, required this.image}) : super(key: key); Widget get decoratedBox { return DecoratedBox( decoration: BoxDecoration(border: Border.all(color: randomColor(), width: 1)), ); } @override Widget build(BuildContext context) { List children = []; for (int i = 0; i < n; i++) { List columnChildren = []; for (int j = 0; j < m; j++) { columnChildren.add( Expanded( child: buildZone(j, i), ), ); } children.add(Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ))); } return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: children, ); } Widget buildZone(int x, int y) { //if (x % 2 != 0 && y % 2 != 0) { //显示点图 if (true) { Color color = Color(image.getPixel(y, x)); color = Color.fromARGB( color.alpha, color.blue, color.green, color.red, ); // return ColorFiltered( // colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.color), // child: ColoredBox(color: color)); return ColoredBox(color: color); } else { return const SizedBox.shrink(); } } // 随机色 final Random random = Random(); Color randomColor({ int limitA = 120, int limitR = 0, int limitG = 0, int limitB = 0, }) { int a = limitA + random.nextInt(256 - limitA); //透明度值 int r = limitR + random.nextInt(256 - limitR); //红值 int g = limitG + random.nextInt(256 - limitG); //绿值 int b = limitB + random.nextInt(256 - limitB); //蓝值 return Color.fromARGB(a, r, g, b); //生成argb模式的颜色 } } 矢量图片

使用点,线和多边形等几何形状来构图,通过数据计算并且绘制而成。具有高分辨率和缩放功能。SVG就是一种矢量图。

还是一样,为了更直观的说明什么时候矢量图,我们还是写一个Demo来表达。使用path_drawing库,这个库可以基于矢量图的路径进行解析。然后把解析出来的路径path使用自定义CustomPainter绘制出来。

原图路径解析绘制fb_logo.svg

了解知识,svg 图片格式中的path指令

M/m (x,y)+ 移动当前位置 L/l (x,y)+ 直线 H/h (x)+ 水平线 V/v (x)+ 竖直线 Z/z 闭合路径 Q/q (x1,y1,x,y)+ 二次贝塞尔曲线 T/t (x,y)+ 光滑绘制二次贝塞尔曲线 C/c (x1,y1,x2,y2,x,y)+ 三次贝塞尔曲线 S/s (x2,y2,x,y)+ 光滑绘制三次贝塞尔曲线 A/a (rx,ry,xr,laf,sf,x,y) 弧线 每个指令都有 大写字母 和 小写字母。 其中 大写字母 表示其后的坐标是 绝对坐标 ,也就是以区域 左上角 为原点的坐标。

附源码

import 'package:flutter/material.dart'; import 'package:path_drawing/path_drawing.dart'; void main() { runApp(const App()); } class App extends StatelessWidget { const App({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('矢量图片'), ), body: const VectorImageTest(), )); } } class VectorImageTest extends StatefulWidget { const VectorImageTest({Key? key}) : super(key: key); @override State createState() => _VectorImageTestState(); } class _VectorImageTestState extends State { final trianglePath = parseSvgPathData( 'M19.4889505,0 C20.7568042,-2.32900945e-16 21.7846027,1.02779849 21.7846027,2.29565218 L21.7846027,25.2043478 C21.7846027,26.4722015 20.7568042,27.5 19.4889505,27.5 C18.2210968,27.5 17.1932983,26.4722015 17.1932983,25.2043478 L17.1929268,22.917 L10.3059268,22.917 L10.3063312,25.2043478 C10.3063312,26.4722015 9.27853273,27.5 8.01067904,27.5 C6.74282535,27.5 5.71502686,26.4722015 5.71502686,25.2043478 L5.71502686,2.29565218 C5.71502686,1.02779849 6.74282535,2.32900945e-16 8.01067904,0 C9.27853273,-2.32900945e-16 10.3063312,1.02779849 10.3063312,2.29565218 L10.3059268,4.583 L17.1929268,4.583 L17.1932983,2.29565218 C17.1932983,1.02779849 18.2210968,2.32900945e-16 19.4889505,0 Z M4.56731796,18.3337402 L4.56731796,22.9170737 L2.84159347,22.9170737 C1.5759409,22.9170737 0.549926758,21.8910595 0.549926758,20.625407 C0.549926758,19.3597544 1.5759409,18.3337402 2.84159347,18.3337402 L4.56731796,18.3337402 Z M24.6584065,18.3337402 C25.9240591,18.3337402 26.9500732,19.3597544 26.9500732,20.625407 C26.9500732,21.8910595 25.9240591,22.9170737 24.6584065,22.9170737 L24.6584065,22.9170737 L22.932682,22.9170737 L22.932682,18.3337402 Z M17.7674484,14.0368652 L9.73266602,14.0368652 L9.73266602,14.6028075 C9.73266602,16.8215514 11.5313133,18.6201987 13.7500572,18.6201987 C15.9688011,18.6201987 17.7674484,16.8215514 17.7674484,14.6028075 L17.7674484,14.6028075 L17.7674484,14.0368652 Z M10.5959038,8.88061523 C9.79988922,8.88061523 9.15863037,9.52187408 9.15863037,10.312907 C9.15863037,11.1039398 9.79988922,11.7451987 10.5909221,11.7451987 L10.5909221,11.7451987 C11.3869367,11.7451987 12.0281956,11.1039398 12.0281956,10.312907 C12.0281956,9.52187408 11.3869367,8.88061523 10.5959038,8.88061523 L10.5959038,8.88061523 Z M16.9090752,8.88061523 C16.1130606,8.88061523 15.4718018,9.52187408 15.4718018,10.312907 C15.4718018,11.1039398 16.1130606,11.7451987 16.9040935,11.7451987 L16.9040935,11.7451987 C17.7001081,11.7451987 18.3413669,11.1039398 18.3413669,10.312907 C18.3413669,9.52187408 17.7001081,8.88061523 16.9090752,8.88061523 L16.9090752,8.88061523 Z M4.56731796,4.58292633 L4.56731796,9.16625977 L2.84159347,9.16625977 C1.5759409,9.16625977 0.549926758,8.14024563 0.549926758,6.87459305 C0.549926758,5.60894047 1.5759409,4.58292633 2.84159347,4.58292633 L2.84159347,4.58292633 L4.56731796,4.58292633 Z M24.6584065,4.58292633 C25.9240591,4.58292633 26.9500732,5.60894047 26.9500732,6.87459305 C26.9500732,8.14024563 25.9240591,9.16625977 24.6584065,9.16625977 L22.932682,9.16625977 L22.932682,4.58292633 L24.6584065,4.58292633 Z'); @override Widget build(BuildContext context) { return Center( child: Transform.translate( offset: const Offset(-150, -150), child: Transform.scale( scale: 10, child: CustomPaint( painter: FilledPathPainter( path: trianglePath, color: Colors.blue, ), ), ), ), ); } } class FilledPathPainter extends CustomPainter { const FilledPathPainter({ required this.path, required this.color, }); final Path path; final Color color; @override bool shouldRepaint(FilledPathPainter oldDelegate) => oldDelegate.path != path || oldDelegate.color != color; @override void paint(Canvas canvas, Size size) { canvas.drawPath( path, Paint() ..color = color ..style = PaintingStyle.fill, ); } @override bool hitTest(Offset position) => path.contains(position); } 图片格式 图片常见格式出生年份表 格式ICOGIFBase64BMPJPEGPNGSVGJPEG2000APNGWebPHEIF出生年份19851987198719901992199619991997 -2000200420102015备注windows 1windows 3链接链接链接链接链接 为什么有这么多图片格式?

我们知道,不同的图片格式有不同的压缩(编码)算法。因此,我认为图片格式的更新迭代,和计算机硬件算力,算法,带宽,版权共同决定的。

光栅图像的本质

我们常说的图像,实质上是由许许多多的像素点组成的二维矩阵。每一个像素点的数据结构如何? 值得我们研究。

1、二值图

二值图像(Binary Image),二值图像的表示只有两种形式,即0和1,其中0代表黑,1代表白。二值图像的保存相对简单,每个像素只需要1Bit就可以完整存储信息,因此二值图像中保存的信息较少。

2、灰度图

灰度图像(gray image)是每个像素只有一个采样颜色的图像,这类图像通常显示为从最暗黑色到最亮的白色的灰度。

灰度化就是使彩色图像的R、G、B分量相等的过程,即令R=G=B,此时的彩色表示的就是灰度颜色。

图片灰度化非常有用,我们常见的图像识别功能,第一步基本上图片灰度化,图像灰度化的目的是为了简化矩阵,提高运算速度,并且RGB三色对图片识别没有什么作用。

图像灰度化处理的几种方式 加权平均法

根据重要性及其它指标,将三个分量以不同的权值进行加权平均。由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到较合理的灰度图像。

平均值法

将彩色图像中的三分量亮度求平均得到一个灰度值。

最大值法

图片灰度化实践

通过以上的公式,可以很容易实现图片灰度化。对于图像识别,有时候灰度化之后,数据量依然很多的话,那还可以把灰度图片转为二值图。实现也简单。大于250/2,存1,小于250/2, 存0。二值图可用于快速计算识别图像轮廓。

原图加权平均法最大值法平均值法

很明显,加权平均法计算出来的灰度图,人眼敏感度最高!

附源码

import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path_drawing/path_drawing.dart'; import 'package:image/image.dart' as img; void main() { runApp(const App()); } class App extends StatelessWidget { const App({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('灰度图片'), ), body: const GrayImageTest(), )); } } class GrayImageTest extends StatefulWidget { const GrayImageTest({Key? key}) : super(key: key); @override State createState() => _GrayImageTestState(); } class _GrayImageTestState extends State { img.Image? image; Future loadImageFromAssets(String path) async { ByteData data = await rootBundle.load(path); List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); return img.decodeImage(bytes); } @override void initState() { super.initState(); initData(); } void initData() async { image = await loadImageFromAssets('assets/image/Lenna.jpg'); final p = image!.getBytes(); for (var i = 0, len = p.length; i < len; i += 4) { //加权平均法 final l = (0.299 * p[i] + 0.587 * p[i + 1] + 0.114 * p[i + 2]).round(); //最大值 //final l = max(max(p[i], p[i + 1]), p[i + 2]); // 平均值 //final l = ((p[i] + p[i + 1] + p[i + 2]) / 3).round(); //分量法1 //final l = p[i]; p[i] = l; p[i + 1] = l; p[i + 2] = l; } if (mounted) setState(() {}); } @override Widget build(BuildContext context) { return Center( child: image == null ? const CircularProgressIndicator() : Image( image: MemoryImage(Uint8List.fromList(img.encodeJpg(image!))), ), ); } } 3、彩色图

彩色图像是指每个像素由R、G、B分量构成的图像。其中R、G、B由不同的灰度级来描述,每个分量的权重都介于[0,255],3字节(24位)可表示一个像素。对于包含透明分类构成的图片,那就是4字节(32位)

4、深度图(Depth)

深度图像(depth image)也被称为距离影像(range image),是指将从图像采集器到场景中各点的距离(深度)作为像素值的图像,它直接反映了景物可见表面的几何形状。

5、RGB-D图像

我们可以认为RGB-D是由两幅图像合在一起构成的,一个是普通的RGB三通道彩色图像,另一个是深度(Depth)图像。需要注意的是RGB-D图像中的RGB图像和Depth图像是严格配准的,即像素之间具有一对一的对应关系。

彩色图片二进制格式解析 图像原始数据

不管是什么格式,或采用什么样的压缩标准,原始的图像数据其实都是一样的。

例如,一张 4 × 4 (宽度和高度都是 4 个像素)的彩色图片,未压缩的的原始图像数据,就是一个 4 × 4 矩形网格,每一个网格代表一个像素。

从上图看出来,图片上的每一个像素,又是由 红,绿,蓝 三基色构成。上图右边所示,红绿蓝,对应于 r g b 三个数值,也就是我常说的 RGB 色彩模式。又称为颜色通道,彩色图像有三个通道值,每个颜色通道,都是一个 0~255 的整数值,占用一个字节(Byte)的存储空间。

因此,我们很容易计算上面这张 4×4 彩色图片占用的存储空间为 4 × 4 × 3 = 48 字节 (Bytes) 。换算成我们熟悉的 KB,就是 48 / 1024 = 0.046875 KB,不到 0.1 KB。

事实上,我们很少见到这么小的图片,甚至在我们的个人电脑和手机上,根本无法正常看到这么小的图片。

按照在电脑上常用的分辨率 72 ppi (Pixels Per Inch:像素每英寸),即 每 2.54 厘米 容纳 72 个像素,或者说,一个像素占用的屏幕尺寸是 0.35 毫米,那么上面 4 × 4 图片,在屏幕上 1:1 显示,占用屏幕的物理尺寸只有 1.4 × 1.4 毫米。显然,用肉眼是无法看清的。

如果以上对于图片大小的计算大家没什么感觉的话,下面我们在继续多一些对比

图片宽高像素4*41024*10243024 * 403224位占用储存空间0.05KB3M34.9M32位占用储存空间0.06KB4M46.5M备注一般图标手机拍照像素

发现没,对于手机拍摄,图片原始数据占用的储存空间是不是有点过大。

如果大家还没啥感觉,我们不妨再用电影举例,一部宽高为 720 × 480,帧率为 30 帧/秒,时长为 1 小时的电影,其原始数据占用的大小大概是:

720 × 480 × 3 (字节/像素) × 30 (帧/秒) × 3600 (秒/小时) × 1小时 = 104.2 GB

是的,以上是图像采集设备采集到的原始数据需要储存空间大小!前文我们已经知道图片有很多种储存格式,其实每一种图片格式代表着不同的压缩算法。图像的压缩或编码,本质就是为了解决图像在存储和网络传输过程的空间消耗,让有限的磁盘和网络带宽,存储和传送海量的数字图像和视频提供了技术后盾。

下面,我们来看看不同图片格式的二进制编码

工具

在查看图片二进制数据之前,先介绍使用的工具

1. 在 linux 和 MacOS 系统上,我们可以借助一个 hexdump 来查看任何二进制文件 hexdump dog.jpeg hexdump -e '16/1 "%02X "' -e '"\n"' dog.jpeg hexdump -e '16/1 "%02X " " | "' -e '16/1 "%_p" "\n"' dog.jpeg 2. Hex Fiend

推荐1:下载链接:hexfiend.com/

3. Vs code

推荐2:安装 Hex Editor 插件

ICO

Windows 1.0时代的桌面图标文件格式,现在主要用户网站图标,一些桌面应用的图标。

00000100 0100``20``20 0000``0100`` 2000A810 00001600 0000

偏移量字节数示例中的值含义020000保留字,都为0220100文件类型,1表示图标,2表示光标420100图像数量,一个icon文件可以有多个图标,这个为16120图像宽度,这个是0x20=32像素,1字节,说明ico的最大宽度是256像素7120图像高度,这个是0x20=32像素,1字节,说明ico的最大高度是256像素8100指定调色板中的颜色数。如果图像不使用调色板,则应为 0。9100保留字,为01020100在 ICO 格式中:指定颜色平面。应该是 0 或 1。在 CUR 格式中:指定热点的水平坐标,以左侧的像素数为单位。1222000在 ICO 格式中:指定每像素的位数。0x20=32位在 CUR 格式中:指定热点的垂直坐标,以距顶部的像素数为单位。144A810 0000指定图像数据的大小(以字节为单位)1841600 0000图标文件起始数据,一般都只1600 0000

目录之后是图标的数据。每个图标的数据可以是没有文件头的BMP图像,也可以是完整的PNG图像(包括文件头)。

GIF

GIF是一种使用LZW压缩,支持多张图像的容器。支持256色,透明通道为1bit。作为互联网表情包的载体,GIF这项80年代的技术依然生生不息。但它的弊端也是显而易见的:易出现毛边,色彩表现低劣,文件压缩比不高。

索引颜色

索引颜色是位图图片的一种编码方法。可以通过限制图片中的颜色总数的方法实现有损压缩。挑选一副图片中最有代表性的若干种颜色(通常不超过256种),编制成颜色表(Color Table)。在存储图片中每一个像素点的颜色信息时,不直接使用这个点的颜色值,而使用颜色表中的索引(Index)。

这样,要表示一幅32位真彩色的图片,使用索引颜色的图片只需要用不超过8位的颜色索引就可以表达同样的信息。使用索引颜色的位图常见的格式有GIF、PNG等。

上面案例gif的图像数据应该存储这些索引值

1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0......

GIF格式二进制图

Header 区块

示例图片

474946``38 3961

字节数示例中的值示例中的含义3474946gif标志,ASCII码。它应该一直是”GIF” (ie 47=”G”, 49=”I”, 46=”F”)338 3961gif版本,示例中是89a, 还有一个97a Logical Screen Descriptor 区块(GIF基本信息)

0A00``0A00`` 910000

字节数示例中的值示例中的含义20A00gif的宽度,gif宽高使用小端编码,即实际宽度为0x000A=10,20A00gif的高度, 2字节,说明gif的最大宽高度是2^16=65536191是一个packed字节,字节91可以表示为二进制数10010001100背景颜色索引值100像素纵横比 GIF每帧数据

This content is only supported in a Feishu Docs

Graphics Control Extension

图形控制扩展块经常用于指定透明度设置和控制动画

21F90405 0A002000

字节数示例中的值示例中的含义221F9固定,标志信息104Byte Size,它的值总是4105是packed field。转为2进制如下Bits 1-3是预留未来用的,值都是0。 Bits 4-6表示处置方法,三位可以代表0~7之间的数字。值1告诉解码器保留此帧图像,并在它的基础上画下一个图像值2代表画布应该恢复为背景颜色.值3代表解码器需要值4-7还未定义,如果图像不是多帧动画,那么值为0. Bit 7是用户输入标志,当为1时代码解码器等待某一个输入才执行下一帧。 Bit 8是透明颜色标志,如果没有使用透明度,则为0,有则为1.20A00延迟时间值,实际时间为(0x000A=10)乘以0.01s=0.1s120透明颜色索引,GIF没有半透明,如果一个图像的颜色索引是255,颜色表中索引255的颜色值是rgb(255,255,255),那么Image Data中存储255索引的像素最终在图像上的值就是rgba(255,255,255,0)100最后一个字节是block终结者,它的值总是00 Image Descriptor

数据流中的每个图像都由一个图像描述符,可选的局部颜色表和图像数据组成。图像描述符可能跟在一个或多个控制区块(如Graphic Control Extension)后面

2C``0100``31 00``8E00``69 0000

字节数示例中的值示例中的含义12C起始标志,每一个图像帧都以一个图像描述符开始。这个block占10个字节。20100Image Left,图像不一定占据logical screen descriptor定义的整个画布。因此就有left和top属性231 00Image Top28E00帧图宽度269 00帧图高度100Packed Field第一位是局部颜色表.如果设置为1允许你指定局部颜色表。 Image Data

图像数据块使用LZW压缩算法,对图片每一帧进行压缩。

Application Extension

第三方应用拓展该规范允许将应用程序特定信息嵌入到GIF文件本身中。都是已0x21FF标志位开头。

重点说一下里面有一个gif播放循坏次数。下图图的值是0,它代表永久循环。

Comment Extension

注释块, 可以在GIF文件中嵌入注释。也许这将是一个有趣的方式来传递秘密信息。这种扩展标签值为0x21FE。

Trailer结尾标志

3B

BMP

位图(Bitmap)格式其实并不能说是一种很常见的格式,因为其数据没有经过压缩,或最多只采用行程长度编码(RLE,run-length encoding)来进行轻度的无损数据压缩。正是因为它没有进行数据压缩,其内部存储的色彩信息(灰度图,RGB 或 ARGB)直接以二进制的形式暴露在外,也十分方便借助计算机软件进行简单或深入的分析。

位图文件头 Bitmap File Header

我们日常生活中遇到的 .bmp 格式图片的文件头长度绝大多数都是 54 字节,其中包括 14 字节的 Bitmap 文件头(Bitmap File Header)以及 40 字节的 DIB (bitmap information header) 数据头。

示例图片

2.bmp

424D``0E0F 0000``0000 00003600 0000 14bytes

索引字节数值值含义①2424D标记代码②40E0F 0000整个.bmp文件的大小(little endian)0x0F0E=3854字节③40000 0000预留字段,通常为0④43600 0000图片信息的开始位置 位图信息文件头DIB header (bitmap information header)

28000000 ``21000000`` DAFFFFFF ``0100`` 1800 00000000 B20E0000 ``130B0000`` 130B0000 00000000 00000000 40字节

示例值偏移位10进制字节数含义28000000144DIB header的大小,通常都是0x28=40字节21000000184以像素为单位的位图宽度(有符号整数)0x21=33DAFFFFFF224以像素为单位的位图高度(有符号整数)为-38,而当高度为负值时,数据块中的行记录与实际图像才是同序的,否则为反序。0100262色彩平面(color plane)的数量(必须为 1)1800282每个像素的位数,即图像的颜色深度。典型值为 1、4、8、16、24 和 32。00000000304正在使用的压缩方法。通常不压缩,对应值为0B20E0000344图像大小。这是原始位图数据的大小;0x0EB2=3762130B0000384图像的水平分辨率。(每米像素,有符号整数)130B0000424图像的垂直分辨率。(每米像素,有符号整数)00000000464调色板中的颜色数,通常为000000000504使用的重要颜色的数量(没啥用),通常为 0,表示每种颜色都重要共40 原始位图数据 Raw Bitmap Data

由于BMP基本上都是没有压缩的,对于RGB为[蓝,绿, 红]排列;对于aRGB图片为[蓝,绿, 红, a],通过读取这个数据,就可以还原一张bmp格式图片了。在解码的时候,每一行需要seek多少个字节呢?通过下面的公式计算就行:

每行的字节数等于:每像素比特数乘以图片宽度加 31 的和除以 32,并向下取整,最后乘以 4。

这样,就很容易计算出原始位图数据的大小为:

PixelArraySize = RowSize · | 图像高度 |

举一个例子:

这样,我们就可以计算出示例图片的RowSize = ( 24 * 33 + 31 ) / 32 * 4 = 100

总数据大小 = 100 * 38 = 3800 + header(54) = 3854。哇!数据刚好对上。

在解析的时候每行 100 / 3 = 33 ... 1,故每行最后会补一个字节的0x00。

JPG

jpg基本上是图片的代名词了。平常我们大部分见到的静态图基本都是这种图片格式。图片能比较好的表现各种色彩,主要在压缩的时候会有所失真,也正因为如此,造就了这种图片格式体积的轻量。

优点: 压缩率高;兼容性好;色彩丰富

缺点: JPEG不适合用来存储企业Logo、线框类的这种高清图;不支持动画、背景透明

1.格式简介

每段数据格式:

FF开头标志位数据长度数据 SOIff d8文件开始APP0ff e0定义交换格式和图像识别信息DQTff db定义量化表SOF0ff c0帧开始DHTff c4霍夫曼(Huffman)表SOSff da扫描行开始EOIff d9文件结束 2. 文件头标识 (2 bytes) (SOI (Start Of Image))

标志位: FFD8

3.文件结束标识 (2 bytes) (EOI)

标志位: FFD9

4. APP0-应用程序保留标记0

标志位:FFE0

FFE00010 ``4A464946 00``0101`` 00 ``0048``0048 0000

字节数示例中的值示例中的含义20010指该块长度为16个字节(不包括FFEO)54A464946 00是JFIF格式标识码,转为ASCII 就是JFIF20101是版本号(第一个01是主版本号,第二个01是次版本号)100X和Y的密度单位 ****00是单位(00=无单位;01=点数/英寸;02=点数/厘米)20048X方向像素密度 **7220048Y方向像素密度**100缩略图水平像素数目,(没有微缩图像,一般都没有,值为0)100缩略图垂直像素数目,(没有微缩图像,值为0)3* N无缩略图RGB位图数据 5. **APPn, Application,应用程序保留标记n,其中n=1~15(任选)

标志位:0xFFE1~0xFFEF

索引字节数值值含义①20xFFE1~0xFFEF标记代码②2②,③字段的总长度,即不包括标记代码,但包括本字段③内容不定详细信息

Adobe Photoshop 生成的JPEG图像中就用了APP1和APP13两个标记段分别存储了一幅图像的副本。

6. SOF0-图像基本信息,帧图像开始(Start of Frame)

标志位: FFCO

FFC00011 08``0780``04 38``03``0122 00021101 031101

字节数示例中的值示例中的含义20011SOF0块长度为17个字节108每个像素的每个颜色分量为8位20780图片高度为1920(直接换算成十进制)2个字节,故jpg最大高度2^16=65536204 38图片宽度1080(直接换算成十进制)103颜色分量数3,JPEG一般采用yCrCb格式,因此最后的组件数量通常为3(每个组件就是像素的一个颜色分量)y指的是亮度;Cr指的是红色分量;Cb指的是蓝色分量。9012200``021101``031101颜色分量信息(重复出现,有多少个颜色分量,就出现多少次(一般为3次))01颜色分量ID 1字节22水平/垂直采样因子 1字节00量化表 1字节 当前分量使用的量化表的ID 7. jpg色彩空间

jpg的色彩空间使用YCbCr

RGBCMYKYCbCrY表示的是亮度,Cb表示的是彩度(蓝),Cr表示的是彩度(红)通常RGB的色彩模型用于显示屏的显示青色、品红、黄色和黑色通常CMYK的色彩模型用于印刷jpg压缩

RGB和YCbCr转换公式

jpg为什么使用YCbCr作为色彩空间?

我们的眼睛之所以能感知图像,是因为人眼内含有视锥细胞和视杆细胞,其中,视锥细胞具有感知颜色的能力,而视杆细胞具有感知亮度的能力,通常,我们的眼睛中,视杆细胞数量相对较多,所以人眼对亮度的敏感程度要高于对色彩的敏感程度。就像你熄灯时,你可以在暗光下渐渐地看清周围的事物,而对周围事物的颜色,你可能就不那么敏感了。

JPEG正是利用了人眼的这一特性,在压缩图像时,将亮度和颜色分开处理。在YCbCr模型中,Cb通道和Cr通道中所包含的信息量远远少于Y通道中包含的信息量。JPEG的压缩算法主要对Cb和Cr通道中的数据进行缩减取样,取样的比例可以是4:4:4(无缩减取样)、4:2:2(在水平方向2的倍数中取样)和4:2:0(在水平方向和垂直方向的2的倍数中取样),其中,以4:2:0最为常见。

8. 应用场景一

我们可能有需求,获取云端图片的宽高,一般的做法是把图片下载下来,然后对图片进行解码,解码之后再获取图片的宽高信息。

有了以上的知识,我们可以使用HTTP请求头Header,添加range=bytes=1-1600,这样就并不需要把图片完整的下载,就可以获取图片的宽高信息。假如我们验证图片宽高符合我们的预期,依然可以设置range=bytes=1601-,下载剩余部分数据,下载完成之后两份数据拼接保存就行。

附代码

final _firstChunkSize = 1600; final url = "http://fanbook-1251001060.cos.accelerate.myqcloud.com/fanbook/app/files/chatroom/image/0a34c1abd07cd2a053b6be03022010b8.jpg"; void getRemoteFile() async { final rep = await _dio.get( url, options: Options( headers: {"range": "bytes=0-$_firstChunkSize"}, responseType: ResponseType.stream, ), ); if (rep.statusCode == 206) { final stream = await (rep.data as ResponseBody).stream.toList(); final result = BytesBuilder(); for (Uint8List subList in stream) { result.add(subList); } final imgData = result.takeBytes(); for (int i = 0; i < imgData.length - 8; i++) { if (imgData[i] == 0xff && imgData[i + 1] == 0xc0) { final h = (imgData[i + 5] _PngIndexColorTestPageState(); } class _PngIndexColorTestPageState extends State { List indexColorList = []; @override void initState() { super.initState(); loadColorData(); } void loadColorData() { String indexStr = ''' 4C697100 A0E9E400 7FE4007F FFF10000 A0E9FFF1 00E4007F E4007F00 A0E900A0 E900A0E9 FFF100E4 007FFFF1 00FFF100 00A0E9FF F100E400 7FFFF100 E4007F00 A0E9E400 7F00A0E9 FFF100E4 007F00A0 E9E4007F E4007F00 A0E9FFF1 00FFF100 00A0E90D 0C0CFFF1 0000A0E9 009F40E6 001E00A0 E900A0E9 E60C10E5 004DE400 7F800F85 FFF100FF F1000099 48E4007F E4007FE4 007F4245 40009A51 009D8AE8 3B0B009B 4300A0E9 E5005B18 248A009F A7E60015 E6003B20 1F1FFFF1 00E6002C 013A95FF F1005448 7F423834 009A61AB 5B3F875D 4B033392 004FA355 775E3534 32595073 009C7324 1F88ED70 0046B232 0A090946 1C870069 B850815F 1AA93A51 6255F7AD 00E60026 0077C30F 0E0EEF7F 00009D95 07A33D55 4E64EB61 00F9BB00 009C82AC CE049306 83493D38 614E44A6 01826F55 49005DAE 55705CE7 220E0F2A 8D2E1E88 584D7AF2 9100E601 4845895D 53443E99 C715009F B5383438 4540496A B82C915E 4900459C 4B544B00 9A5A4B1B 87BDD400 E94F0684 C0235A1A 863D1D87 9F5E4625 2322534D 60242322 6C168674 574A77BC 28681786 3C373BF4 A1004B46 524E4958 E94609F1 8A00009B 73413C43 B65738E7 1E0F84C1 29585070 F49E0063 B62F2D8E 5A494085 777759E6 00120000 00E4007F 00A0E9FF F1000099 441D2088 FFFFFFF9 D4E3F19E C2EA609E FFF352FF F20CFFE7 0001B4ED FCEBF2F3 ACCBE738 8EF5EB00 00974CFB DFEBDD20 19079355 E94C96F5 F6F62321 217FCEF4 E5006800 A5EAE500 73C8E8FA 009AE3FF FDEAED80 B000A0C1 EF8FB92B 2887D63A 2000AFEC A7DBF7F3 FAFEE400 7A0085D0 00A0DF00 A9EBE61C 87FFFDD4 FFF89BFE F5F9FFFE F5FFFAB7 E6F4FD49 3D84F5BA D3F7C7DB FCC800FF F5785653 53008DD7 D7EEFCFF DD00EC70 A7413786 EBECEC00 A0CBCFDB 00DFE0E0 45424166 64649FA0 A0BE0081 00A0D648 C0F093D5 F6EBE600 D2008024 BAEFD4D4 D5DDE000 C34F2FCC 46283630 8763C7F2 C8008100 94DEB7E2 F9838383 B3008291 9292ADAE AEBABBBB C7C8C8FD D200DB00 7F757474 288E5AFF F689FFFB C4FFF465 FFF332B8 5637C0D5 00504382 ''' .replaceAll(" ", ""); List index = []; for (int i = 0; i < indexStr.length - 2; i += 2) { final item = indexStr.substring(i, i + 2); int colorItem = int.parse("0x$item"); debugPrint("colorItem: $colorItem"); index.add(colorItem); } indexColorList.clear(); indexColorList.addAll(index); debugPrint("indexColorList length: ${indexColorList.length}"); if (mounted) setState(() {}); } @override Widget build(BuildContext context) { if (indexColorList.isEmpty) return Container(); //768 / 3 = 256 索引颜色一个256个 List children = []; for (int i = 0; i < 16; i++) { List columnChildren = []; for (int j = 0; j < 16; j++) { final startIndex = i * 16 + j; columnChildren.add( Expanded( child: buildZone(indexColorList[startIndex], indexColorList[startIndex + 1], indexColorList[startIndex + 2]), ), ); } children.add(Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ))); } return Center( child: Padding( padding: const EdgeInsets.only(top: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ const Text("原图"), Image.asset( "assets/image/RGB-02.png", width: 80, height: 80, ), ], ), const SizedBox(width: 10), SizedBox( width: 800, height: 800, child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: children, ), ), ], ), )); } Widget buildZone(int r, int g, int b) { final color = Color.fromARGB( 0xFF, r, g, b, ); return Stack( children: [ Positioned.fill( child: ColoredBox(color: color), ), Positioned.fill( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "r:$r", style: const TextStyle(fontSize: 9, color: Colors.white), ), Text( "g:$g", style: const TextStyle(fontSize: 9, color: Colors.white), ), Text( "b:$b", style: const TextStyle(fontSize: 9, color: Colors.white), ), ], )) ], ); } } 图片压缩数据IDAT

字节数示例中的值示例中的含义400002000数据块长度 00002000=8192 字节449444154数据块类型码 “IDAT” 的 ASCII 字母8192...图片数据4CRC (循环冗余检测)

IDAT块包含实际的图像数据,它是压缩算法的输出流。详见第 9 条:过滤和第 10 条:压缩。

图像结束标识IEND

00000000 49454E44 AE426082 共12字节。

字节数示例中的值示例中的含义400000000数据库长度=0449454E44数据标识总是IEND(49 45 4E 44)0数据库4AE426082CRC

当IEND数据块被找到时,这个PNG图像才认为是合法的PNG图像。

WebP

是一种新的图片格式,可提供出色的无损和有损压缩,对于Web开发来说,可以创建更小和更丰富的图像。根据官网测试,WebP无损压缩的图片比PNG格式图片,文件大小上少 26%,WebP有损图片在同样 SSIM 质量指标上比JPEG格式图片少25~34%,SSIM是一种衡量两张数字影像相似的指标。

压缩举例

WebP和Png大小对比Gif和Animated WebP大小对比WebP和Jpg大小对比在线示例在线示例在线示例在线示例2

科技博客 GigaOM 曾报道:YouTube 的视频略缩图采用 WebP 格式后,网页加载速度提升了 10%;谷歌的 Chrome 网上应用商店采用 WebP 格式图片后,每天可以节省几 TB 的带宽,页面平均加载时间大约减少 1/3;Google+ 移动应用采用 WebP 图片格式后,每天节省了 50TB 数据存储空间。

根据Google的测试,目前WebP与JPG相比较,编码速度慢10倍,解码速度慢1.5倍。

在编码方面,一般来说,我们可以在图片上传时生成一份WebP图片或者在第一次访问JPG图片时生成WebP图片,对用户体验的影响基本忽略不计,主要问题在于1.5倍的解码速度是否会影响用户体验。

下面通过同样质量(SSIM)的WebP与JPG图片加载的速度进行测试。

从折线图可以看到,WebP虽然会增加额外的解码时间,但由于减少了文件体积,缩短了加载的时间,页面的渲染速度加快了。同时,随着图片数量的增多,WebP页面加载的速度相对JPG页面增快了。

二进制格式解析

实例图片

1.webp 储备知识 RIFF 资源互换文件格式(Resources Interchange File Format)

它是在1991年时,由 Microsoft 和 IBM提出。是一种把资料储存在被标记的区块(tagged chunks)中的档案格式(meta-format)。 RIFF使用的是 小端序(little-endian)。

'RIFF'类型的chunk结构如下 structchunk{ u32 id; /* 块标志 */ u32 size; /* 块大小 */ /*此时的dat = type + restdat */ u32 type ; /* 类型 */ u8 restdat[size] /* dat中除type4个字节后剩余的数据*/ };

WebP文件格式基于RIFF(资源交换文件格式)文档格式。

FourCC (four-character code):32 bits, 代表四字符编码字符串。比如WebP(0x``57454250)

Chunk Size: 32 bits , 数据块(Chunk Payload)大小,不包括自己和FourCC

Chunk Payload: ****数据块

WebP Chunk类型 ChunkType描述VP8Xdescriptions of features used。包含图片的宽高,是否透明,是否动画等信息ALPHalpha bitstream,透明通道数据流VP8bitstream,表示有损压缩数据流VP8Llossless bitstream 表示无损压缩数据流ICCPcolor profile 颜色配置XMPmetadataANIMglobal animation parameters 全局动画信息,比如动画循环次数ANMFframe1 parameters + data, 每一帧动画参数和数据EXIFmetadata,可交换图像文件,保存相机参数,位置,版权等信息

带有alpha通道有损编码图像可能如下所示

RIFF/WEBP +- VP8X (descriptions of features used) +- ALPH (alpha bitstream) +- VP8 (bitstream)

无损编码图像可能如下所示:

RIFF/WEBP +- VP8X (descriptions of features used) +- VP8L (lossless bitstream)

具有ICC配置文件和XMP元数据的无损图像可能如下所示:

RIFF/WEBP +- VP8X (descriptions of features used) +- ICCP (color profile) +- VP8L (lossless bitstream) +- XMP (metadata)

带有Exif元数据的动画图像可能如下所示:

RIFF/WEBP +- VP8X (descriptions of features used) +- ANIM (global animation parameters) +- ANMF (frame1 parameters + data) +- ANMF (frame2 parameters + data) +- ANMF (frame3 parameters + data) +- ANMF (frame4 parameters + data) +- EXIF (metadata) WebP标志头

52494646`` ``94120000`` ``57454250

字节数示例中的值示例中的含义452494646代表RIFF的ASCII码,RIFF是一个存数据的容器494120000wepb使用小端编码, 这部分代码总数据大小 0x1294=4756 + 8 = 4764457454250代表WEBP的ASCII码 图片描述信息VP8X

56503858`` 0A000000 10000000 C70000C7 0000

字节数示例中的值(16进制)示例中的含义456503858代表VP8X的ASCII码40A000000数据块长度 0x0A = 10110转为2进制为:00010000,一共8个bite。含义如下00: 保留位, 2bit0: Set if the file contains an ICC profile. ICC1: 是否为透明图片,alpha0:是否包含Exif信息(Exif metadata)0:是否包含XMP信息( XMP metadata)0: 是否为动画图片,为1说明Data in 'ANIM' and 'ANMF' chunks0: 保留位3000000保留字段,都为03C70000****Canvas Width **实际宽度为:1 + 0xC7 = 200。**WebP 与 VP8 比特流兼容,并使用 14 位来表示宽度和高度。WebP 图像的最大像素尺寸为 16383 x 163833C70000Canvas Height 实际高度度为:1 + 0xC7 = 200 ALPH 图片alpha通道数据

414C5048`` B7090000 ``01``F74736 6993E4FF D34B......

字节数示例中的值(16进制)示例中的含义4414C5048代表ALPH的ASCII码4B7090000数据块长度 0x9B7 = 2487(奇数的话,需要在数据库末尾补0,让长度变成偶数。)101转为2进制为:00000001,一共8个bite。含义如下00: 保留位, 2bit00: 数据是否已经预处理,0没有预处理,100: 滤波方式01: 压缩方式,0:没有压缩,1: WebP无损压缩2487...alpha通道数据流 ANIM图片动画信息

示例图片

2.webp

414E494D`` ``06000000`` ``FFFFFF00`` ``0000

字节数示例中的值(16进制)示例中的含义4414E494D代表ANIM的ASCII码406000000数据块长度 0x06 = 6字节4FFFFFF00画布的默认背景颜色,以 [Blue, Green, Red, Alpha] 字节顺序排列#``FFFFFF00,白色透明背景20000动画循环次数,0代表无限循环 ANMF每帧动画图像

414E4D46`` ``D6010000`` 780000 ``5A 0000``7502 00``650300`` ``640000``03 5650384C......

字节数示例中的值(16进制)示例中的含义4414E4D46代表ANMF的ASCII码4D6010000数据块长度 0x01D6 = 470字节3780000帧 X, 实际左上角的 X 坐标为**Frame X * 2**Offx = 0x78 * 2 = 24035A 0000帧 Y, 实际左上角的 Y 坐标为Frame Y * 2Offy = 0x5A * 2 = 18037502 00帧宽减一实际宽度为1 + 0x0275 = 6303650300帧高减一实际高度为1 + 0x0365 = 8703640000帧持续时间,显示下一帧之前的等待时间,以 1 毫秒为单位0x64/1000=0.1s103转为2进制为:0000001``1,一共8个bite。含义如下000000: 保留位,必现为01:混合方式(B), 指示当前帧的透明像素如何与前一个画布的相应像素混合:- 0:使用 Alpha 混合。处理前一帧后,使用 alpha 混合在画布上渲染当前帧。如果当前帧没有 alpha 通道,假设 alpha 值为 255,有效地替换了矩形。- 1: 不要混合。处理前一帧后,通过覆盖当前帧覆盖的矩形在画布上渲染当前帧。1:处理方法(D), 指示当前帧在画布上显示后(在渲染下一帧之前)如何处理:- 0: 不要丢弃。让画布保持原样。- 1:设置为背景颜色。用ANIM 块中指定的背景颜色 填充当前帧覆盖的画布上的矩形。470-16...帧数据

附参考文档:

图像文件类型与格式指南 - Web 媒体技术 | MDN

zhuanlan.zhihu.com/p/25119530

en.wikipedia.org/wiki/BMP_fi…

www.cnblogs.com/Matrix_Yao/…

cloud.tencent.com/developer/a…

www.techug.com/post/unders…

en.wikipedia.org/wiki/ICO_(f…

developers.google.cn/speed/webp/…

www.zhoulujun.cn/html/theory…

blog.csdn.net/hherima/art…

www.w3.org/TR/2003/REC…

www.media.mit.edu/pia/Researc…

en.wikipedia.org/wiki/JPEG_F…

blog.csdn.net/sinat_26472…

blog.csdn.net/lpt19832003…

www.cnblogs.com/yongdaimi/p…

www.zhoulujun.cn/html/theory…

cloud.tencent.com/developer/a…

www.zhoulujun.cn/html/theory…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3