IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【AVD】C++ 不解码获取 JPG 图片宽高、旋转信息等 EXIF 信息 -> 正文阅读

[C++知识库]【AVD】C++ 不解码获取 JPG 图片宽高、旋转信息等 EXIF 信息

前段时间写过一篇 FFmpeg 获取 JPG 图片旋转信息等 Exif 信息。但有些时候,我们并不想,或者暂时不想解码图片,而只想获取图片的宽高等相关信息。而上篇文章中提到的使用 FFmpeg 解码后获取的图片旋转信息的方法无疑要增加图像解码过程,看起来好像稍微慢了些。因此,我们有必要找个方法避免解码后才能获得图片信息。

ffprobe

这里有个疑问,感觉当我们使用 ffprobe 命令行 ffprobe -i xxx.jpg -show_streams -show_format 查看该图片的信息时,ffprobe 是能正常展示这张 jpg 图片的宽高的,但是从解码过程来看,整个过程中的 AVStream->codecpar 中的 width height 却都是 0。包括,FFmpeg 解析 png 时也是。因此,有必要看一下 ffprobe 的源码,看一下它是如何正确地打印出图片的宽高的。

JFIF & EXIF

参考这篇文章 JPEG文件格式 JFIF & Exif 可知,世界上有两种 jpg 图片文件格式头,一种是较为简单的 JFIF 格式,仅存储了图像精度、高度、宽度、颜色分量数及颜色分量信息;另一种是比较复杂的 EXIF 格式,存储了各种各样的信息,包括了从相机制造商到光圈大小、图像旋转信息、拍摄时间等。

对于 JFIF 格式的 JPG,其实我们看一下 JPEG Start of Frame marker 的结构,也能自己通过读文件的方法读取出该 JPG 图片的宽高。

但是对于 EXIF 格式的头部信息,则略显复杂。因此,我找了下现有的开源库,发现了 CExif 这个类。

Cexif 类

这个类是我从 CSDN 下载的,从代码上的注释来看,应该是个意大利程序员开发的。我从 GitHub 上找了下,没有相关的 repository,于是自己创建了个:CExif

这个类的用法很简单,创建一个 Cexif 实例,把 JPG 文件的句柄 FILE * 作为参数传递给类成员函数 bool DecodeExif,然后即可访问类的 public 成员结构体变量 m_exifinfo,可以用 m_exifinfo->Orientation 获取 JPG 图像的方向信息,进而通过方向定义解析出 JPG 图片的旋转信息。

  /*
    1 = Horizontal (normal)
    2 = Mirror horizontal
    3 = Rotate 180
    4 = Mirror vertical
    5 = Mirror horizontal and rotate 270 CW
    6 = Rotate 90 CW
    7 = Mirror horizontal and rotate 90 CW
    8 = Rotate 270 CW
  */

具体方法如下:

Cexif c;
  FILE *f = fopen(filename.c_str(), "rb");
  if (!f) {
    AVLOGE("Error open file %s, errno %d.", filename.c_str(), errno);
    return false;
  }
  c.DecodeExif(f);
  fclose(f);
  width_ = c.m_exifinfo->Width;
  height_ = c.m_exifinfo->Height;
  switch (c.m_exifinfo->Orientation) {
    case 1:
    case 2:
      rotation_ = 0;
      break;
    case 3:
    case 4:
      rotation_ = 180;
      break;
    case 5:
    case 8:
      rotation_ = 270;
      break;
    case 6:
    case 7:
      rotation_ = 90;
      break;
    default:
      rotation_ = 0;
      break;
  }

再快一些

在 Linux 上使用 vim 查看一张 jpg 图片(当然也可以是任何文件),然后输入 :%!xxd 即可以十六进制查看该图片文件的具体字节信息。如下图所示:
在这里插入图片描述
通过使用 Windows 正常图片浏览器打开该图片获得它的宽高。通过读 Cexif 的源码也能分析到。一个 JPG 图片的最开头的三个字节是 0x FF D8 FF,再后面的 E1 标识了它是使用 APP1 marker,也就是使用 Exif Attribute Information 来记录相关信息的。还有些 JPG 的第四个字节可能是 0xC0,也就是 SOFn(Start of Frame) 节,也记录了图片的宽高。

先看 Exif 格式中,紧随着的 0x0C07 标识了该 EXIF 信息节的长度是十六进制数 C07 对应的十进制,即 3079 个字节那么大。于是 Cexif 在读到文件的第四个字节是 E1 后,会再去读两个字节,来判断 Exif 节的长度 len,然后接着读取 len - 2 个字节,因为这个 len 其实是包括了 0x0C07 这两个字节在内的。

再往下读,四个字节,0x 45 78 69 66 对应的 ASCII 码刚好是 Exif 这四个字符。再往后一些的 0x4d4d 对应于 ASCII 码 MM,即 motorola 存放顺序;如果对应于 II 则是 Intel 存放顺序。略过一些,当读到 0x0112 时,则表示读到了 Orientation 信息,不同的信息用 Tag 表示,后面的 0x0003 表示了标识信息 Orientation 的数据格式 Format,再往后读4个字节,则是其内容个数 Components。

通过 Cexif 可以,Format 主要有以下几种:

nameSBYTEBYTEUSHORTULONGURATIONALSRATIONALSSHORTSLONG
enumvalue613451089
typesigned char *unsigned char *16u32u32s/32s32s/32ssigned short32s
nb_bytes11248824

因此,Orientation 对应的 format 3 的意思是它是个 ushort,要读 2 个字节。而它的 components 是 1,也就是只有一个信息。于是往后再读两个字节刚好读到 0x0006,即对应于这张 JPG 图片的 Orientation 信息是 6。

同理,从 Cexif 的源码可知,0xa003 表示图像高度,用 4 也就是 4 个字节的 32 位无符号表示,有 1 个,再往后读 4 个字节,即 0x08dc,即 2268。0xa002 表示图像宽度,不再赘述。

然而,从 Cexif 的源码中并未看到有读取上述图像高度和图像宽度的处理。通过断点发现 Cexif 在读完 Exif 节信息后,还在不停地继续向后读文件。即 EXIF.cpp 45 行的 for(;;) 循环。但是它读到的内容,能处理的并不多,即命中 cpp 105 行那个 marker 的 case 并不多。因此,可以在这里做一个加速,先判断是否是后面的 case,再决定是否要将数据读出来 (fread)。在遇到并不能处理的数据块时,可以直接跳过 (fseek) 相应的内存大小。因此,我对 cpp 中这部分代码做了如下优化,这里,我们需要的就只有 EXIF 来读取图片方向信息,SOF 来读取图片宽高,以及 SOS 来确保这个 for(;;) 循环能在读到图像数据之前停下来:

    // Read the length of the section.
    lh = fgetc(hFile);
    ll = fgetc(hFile);

    itemlen = (lh << 8) | ll;

    if (itemlen < 2) {
      strcpy(m_szLastError, "invalid marker");
      return 0;
    }

    if (marker != M_EXIF && marker != M_SOS && (marker < M_SOF0 || marker > M_SOF15)) {
      fseek(hFile, itemlen - 2, SEEK_CUR);
      continue;
    }

    Sections[SectionsRead].Size = itemlen;

此外,由于我们只需要从 EXIF 信息中读取 Orientation 信息,即 case TAG_ORIENTATION:,因此对于其他 case 也可以用 #if 0 #endif 来控制不编译。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-23 15:34:46  更:2021-12-23 15:35:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:08:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码