JPEG/Exif/TIFF格式解读(1):JEPG图片压缩与存储原理分析

JPEG文件简介

JPEG的全称是JointPhotographicExpertsGroup(联合图像专家小组),它是一种常用的图像存储格式, jpg/jpeg是24位的图像文件格式,也是一种高效率的压缩格式,文件格式是JPEG(联合图像专家组)标准的产物,该图像压缩标准是国际电信联盟(International Telecommunication Union,ITU)、国际标准化组织(International Organization for Standardization,ISO)和国际电工委员会(International Electrotechnical Commission,IEC)共同制定。JPEG标准正式地称为ISO/IEC IS(国际标准)10918-1:连续色调静态图像数字压缩和编码(Digital Compression and Coding of Continuous-tone Still Images)和ITU-T建议T.81。

JPEG是第一个国际图像压缩标准,用于连续色调静态图像(即包括灰度图像和彩色图像),其最初目的是使用64Kbps的通信线路传输720×576 分辨率压缩后的图像。通过损失极少的分辨率,可以将图像所需存储量减少至原大小的10%。由于其高效的压缩效率和标准化要求,目前已广泛用于彩色传真、静止图像、电话会议、印刷及新闻图片的传送上。但那些被删除的资料无法在解压时还原,所以* .jpg/.jpeg文件并不适合放大观看,输出成印刷品时品质也会受到影响。

JPEG文件格式

JPEG的文件格式一般有两种文件扩展名:.jpg和.jpeg,这两种扩展名的实质是相同的,我们可以把.jpg的文件改名为.jpeg,而对文件本身不会有任何影响。严格来讲,JPEG的文件扩展名应该为.jpeg,由于DOS时代的8.3文件名命名原则,就使用了.jpg的扩展名,这种情况类似于.htm和.html的区别。

JPEG标准不指定任何固有的文件格式。它只定义压缩比特流的语法。这就产生了一定数量的文件格式来存储JPEG压缩后的图像,例如JPEG文件交换格式(JPEG File Interchange Format,JFIF),JPEG推广到TIFF6.0、FlashPix等。但它们中的每一个都不能认为是由国际标准委员会支持的正式定义的国际标准。

JPEG格式可以分为标准JPEG渐进式JPEGJPEG2000三种格式。

标准JPEG:该类型的图片文件,在网络上应用较多,只有图片完全被加载和读取完毕之后,才能看到图片的全貌;它是一种很灵活的图片压缩方式,用户可以在压缩比和图片品质之间进行权衡。不过,通常来讲,其压缩比在10:1到40:1之间,压缩比越大,品质就越差,压缩比越小,品质就越好。JPEG格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网,可减少图像的传输时间,可以支持24bit真彩色,也普遍应用于需要连续色调的图像。JPEG由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度。其压缩模式有以下几种:

顺序式编码(SequentialEncoding)

递增式编码(ProgressiveEncoding)

无失真编码(LosslessEncoding)

阶梯式编码(HierarchicalEncoding)

JPEG的压缩步骤

颜色转换:由于JPEG只支持YUV颜色模式,而不支持RGB颜色模式,所以在将彩色图像进行压缩之前,必须先对颜色模式进据转换。转换完成之后还需要进行数据采样。一般采用的采样比例是2:1:1或4:2:2。由于在执行了此项工作之后,每两行数据只保留一行,因此,采样后图像数据量将压缩为原来的一半。

DCT变换:DCT(DiscreteConsineTransform)是将图像信号在频率域上进行变换,分离出高频和低频信息的处理过程。然后再对图像的高频部分(即图像细节)进行压缩,以达到压缩图像数据的目的。首先将图像划分为多个8*8的矩阵。然后对每一个矩阵作DCT变换(变换公式此略)。变换后得到一个频率系数矩阵,其中的频率系数都是浮点数。

量化:由于在后面编码过程中使用的码本都是整数,因此需要对变换后的频率系数进行量化,将之转换为整数。由于进行数据量化后,矩阵中的数据都是近似值,和原始图像数据之间有了差异,这一差异是造成图像压缩后失真的主要原因。

编码:编码采用两种机制:一是0值的行程长度编码;二是熵编码(EntropyCoding)。在JPEG中,采用曲徊序列,即以矩阵对角线的法线方向作“之”字排列矩阵中的元素。这样做的优点是使得靠近矩阵左上角、值比较大的元素排列在行程的前面,而行程的后面所排列的矩阵元素基本上为0值。行程长度编码是非常简单和常用的编码方式,在此不再赘述。编码实际上是一种基于统计特性的编码方法。在JPEG中允许采用HUFFMAN编码或者算术编码。

更详细可以参看《色彩空间RGB/CMYK/HSL/HSB/HSV/Lab/YUV基础理论及转换方法:RGB与YUV》、《视频采样,量化,编码,压缩,解码相关技术原理学习笔记

Baseline JPEG/基本JPEG:这种类型的JPEG文件存储方式是按从上到下的扫描方式,把每一行顺序的保存在JPEG文件中。打开这个文件显示它的内容时,数据将按照存储时的顺序从上到下一行一行的被显示出来,直到所有的数据都被读完,就完成了整张图片的显示。这种图片在web中,如果没有给图片指定宽高,会造成重绘。

progressive jpeg/渐进式JPEG:JPEG文件包含多次扫描,这些扫描顺寻的存储在JPEG文件中。打开文件过程中,会先显示整个图片的模糊轮廓,随着扫描次数的增加,图片变得越来越清晰。该类型的图片是对标准JPEG格式的改进,当在网页上下载渐进式JPEG图片时,首先呈现图片的大概外貌,然后再逐渐呈现具体的细节部分,因而被称之为渐进式JPEG。这种通过HTTP2 多路复用传递渐进式JPEG的扫描图层来提高感知性能和速度指数的方式已经早在2012年被Google的John Mellor注意到了。他一直在实验SPDY协议,HTTP2的前身。

JPEG2000:一种全新的图片压缩发,压缩品质更好,并且改善了无线传输时,因信号不稳定而造成的马赛克及位置错乱等问题。另外,作为JPEG的升级版,JPEG2000的压缩率比标准JPEG高约30%,同时支持有损压缩和无损压缩。它还支持渐进式传输,即,先传输图片的粗略轮廓,然后,逐步传输细节数据,使得图片由模糊到清晰逐步显示。此外,JPEG2000还支持感兴趣区域,也就是说,可以指定图片上感兴趣区域的压缩质量,还可以选择指定的部分先进行解压。还有个优势就是,JPEG2000从无损压缩到有损压缩可以兼容

压缩步骤

由于JPEG的有损压缩方式(Lossy mode of operation)并不比其他的压缩方法更优秀,

因此我们着重来看它的有损压缩中最常用的基线JPEG算法(baseline sequential)。以一幅24位彩色图像为例,JPEG的压缩步骤分为:

颜色转换

JPEG支持图像采用任何一个色彩空间,支持1~4个颜色分量。灰度图像颜色分量数为1。RGB、YUV、YCbCr等拥有3种颜色分量。4种颜色分量的例子是青、洋红、黄和黑(Cyan,Magenta,Yellow,and Black,CMYK)。为了减少色度通道包含的大量的冗余信息,本例中采用YCbCr色彩空间。首先需要进行从RGB到YCbCr的色彩空间变换:

Y = 0.299000R + 0.587000G + 0.114000B

Cb = -0.168736R - 0.331264G + 0.500002B

Cr = 0.500000R - 0.418688G - 0.081312B

其中,Y表示亮度分量,Cb和Cr表示蓝红色度分量。

DC电平偏移

最初,在图像中的像素存储在无符号的整数中。对于数学计算,在图像中任何变换或数学计算开始之前,根本上是将这些采样转换成两个补码表示。DC电平偏移的目的是保证输入图像的采样有近似地集中在零附近的动态范围。DC电平偏移执行的图像采样只通过无符号数表示。

方法:假设图片分量的采样精度为n,那么分量中的每个像素值应减去2的(n-1)次幂。

对于图像而言他的采样由无符号的整数表示,例如CT(X光断层成像)图像,动态范围已经集中于零附近,所以不需要DC电平偏移。

子采样

色彩空间转换之后,图像的大多数空间信息包含在亮度分量Y中。色度分量Cb和Cr包含大量冗余的颜色信息,所以我们运用子采样较少色度数据量以在丢失少量信息的情况下压缩图像。基线JPEG常用的子采样格式为4:2:0,同时支持4:2:2和4:4:4颜色格式。

DCT变换

DCT(DiscreteCosineTransform)是将图像信号在频率域上进行变换,分离出高频和低频信息的处理过程。然后再对图像的高频部分(即图像细节)进行压缩,以达到压缩图像数据的目的。首先将图像划分为多个8*8的矩阵。然后对每一个矩阵作DCT变换。变换后得到一个频率系数矩阵,其中的频率系数都是浮点数。

量化

由于在后面编码过程中使用的码本都是整数,因此需要对变换后的频率系数进行量化,将之转换为整数。由于进行数据量化后,矩阵中的数据都是近似值,和原始图像数据之间有了差异,这一差异是造成图像压缩后失真的主要原因。

在这一过程中,质量因子的选取至为重要。值选得过大,可以大幅度提高压缩比,但是图像质量就比较差;反之,质量因子越小(最小为1),图像重建质量越好,但是压缩比越低。对此,ISO已经制定了一组供JPEG代码实现者使用的标准量化值。

右图的两个量化表的设计是根据由Lohscheller做的心理视觉实验来确定二维基函数的可见阈值。

编码

从前面过程我们可以看到,颜色转换完成到编码之前,图像并没有得到进一步的压缩,DCT变换和量化可以说是为编码阶段做准备。

编码采用两种机制:一是0值的行程长度编码;二是熵编码(EntropyCoding)。

之字形排序(Zig-zag ordering)

在JPEG中,采用曲徊序列,即以矩阵对角线的法线方向作“之”字排列矩阵中的元素。这样做的优点是使得靠近矩阵左上角、值比较大的元素排列在行程的前面,而行程的后面所排列的矩阵元素基本上为0值。

使用RLE对交流系数(AC)进行编码

行程长度编码是非常简单和常用的编码方式,在此不再赘述。

需要注意的是,AC系数的之字形序列编码中有两个特殊符号——(0,0)和(15,0)。第一个特殊符号指的是块的结束(end-of-block,EOB),用来表明在之字形块中剩余的元素都是零。另一个特殊符号是指零游程长度(zero-run-length,ZRL),用来表明16个零游程。基线JPEG允许的零游程最大长度是16个。如果这里的零超过16个,那么这个游程分成几个长度为16的零游程。

使用DPCM对直流系数(DC)进行编码

DCT系数量化之后,通过差分编码对量化后的DC系数编码。当前块的DC系数减去前个块的DC系数,然后对其差值进行编码,如右图所示。这就利用了邻接块DC值之间的空间相关性。

熵编码:编码实际上是一种基于统计特性的编码方法。在JPEG中允许采用HUFFMAN编码或者算术编码。而基线JPEG算法(baseline sequential)采用的是前者。

经过RLE编码的AC系数可以映射成两个标志(RUNLENGTH,CATEGORY)和(AMPLITUDE),前者采用的是霍夫曼编码,而后者采用的是VLI编码。同理经过DPCM编码的DC系数同样可以映射成两个标志(CATEGORY)和(AMPLITUDE),前者采用霍夫曼编码,后者采用VLI编码。

基线JPEG允许使用4个霍夫曼表,两个用于AC系数编码,两个用于DC系数编码。

如何识别JEPG文件的

其实很简单,就是判断前面3个字节是什么,如果发现是FF D8 FF开始,那就认为它是JEPG图片。

JPG文件是由一段段的数据构成的组成的(segment),段的多少和长度并不是一定的。只要包含了足够的信息,该JPEG文件就能够被打开。

JPEG格式和标记

JPEG图片格式组成部分:SOI(文件头)+APP0(图像识别信息)+ DQT(定义量化表)+ SOF0(图像基本信息)+ DHT(定义Huffman表) + DRI(定义重新开始间隔)+ SOS(扫描行开始)+ EOI(文件尾)

以16进制模式打开JPG文件,就会发现

JPEG 文件中有一些形如 0xFF** 这样的数据,它们被称为“标志(Marker)”,它表示 JPEG 信息数据段。例如 0xFFD8 代表 SOI(Start of image), 0xFFD9 代表 EOI(End of image)。

标志 0xFFE0~0xFFEF 被称为 "Application Marker",它们不是解码 JPEG 文件必须的,可以被用来存储配置信息等。EXIF 也是利用这个标志段来插入信息的,具体来说,是 APP1(0xFFE1) Marker。所有的 EXIF 信息都存储在该数据段。

------------------

名称  标记码  说明

------------------

SOI   D8     文件头

EOI   D9     文件尾

SOF0  C0     帧开始(标准 JPEG)

SOF1  C1     同上

DHT   C4     定义 Huffman 表(霍夫曼表)

SOS   DA     扫描行开始

DQT   DB     定义量化表

DRI   DD     定义重新开始间隔

APP0  E0     定义交换格式和图像识别信息

DNL   DC     标记码

COM   FE     注释

段类型有30种,但只有10种是必须被所有程序识别的,其它的类型都可以忽略。

JPEG format and Marker

SOI MarkerMarker XX size=SSSSMarker YY size=TTTTSOS Marker size=UUUUImage streamEOI Marker

FFD8FFXXSSSSDDDD......FFYYTTTTDDDD......FFDAUUUUDDDD....I I I I....FFD9

0xFFE0~0xFFEF之间的标记被叫做 "应用标记", 它们在JPEG图像解码中不是必须存在的. 它们被使用于用户的应用程序之中. 例如, 老款的olympus/canon/casio/agfa 数字相机使用 JFIF(JPEG文件交换格式/JPEG File Interchange Format)来存储图像. JFIF 使用 APP0(0xFFE0) 标记来插入数字相机的配置信息数据和缩略图.

Exif也使用应用标记来插入数据, 但是Exif 使用 APP1(0xFFE1)标记来避免与JFIF格式的 冲突. 且每一个 Exif 文件格式都开始于它, 如

SOI 标记标记 XX 的大小=SSSS标记 YY 的大小=TTTTSOS 标记 的大小=UUUU图像数据流EOI 标记

FFD8FFXXlo0pSSSSDDDD......FFYYTTTTDDDD......FFDAUUUUDDDD....I I I I....FFD9

Exif也使用应用标记来插入数据, 但是Exif 使用 APP1(0xFFE1)标记来避免与JFIF格式的 冲突. 且每一个 Exif 文件格式都开始于它, 如;

Marker used by Exif

0xFF+Marker Number(1 byte)+Data size(2 bytes)+Data(n bytes)

SOI MarkerAPP1 MarkerAPP1 DataOther Marker

FFD8FFE1SSSS 457869660000 TTTT......FFXX SSSS DDDD......

该图像文件从SOI(0xFFD8) 标记开始, 因此它是一个 JPEG 文件. 后面马上跟着 APP1 标记. 而它的所有 Exif数据都被存储在 APP1 数据域中. 上面的 "SSSS" 这部分表示 APP1 数据域 (Exif data area)的大小. 请注意这里的大小 "SSSS" 包含描述符本身的大小.

在 "SSSS"后面, 是 APP1 的数据. 其中第一个部分是一个特殊的数据,它用来标识是否是 Exif, 其值是ASCII 字符 "Exif" 和 两个0x00字节 的组合字符串.

在 APP1 标记域的后面是, 跟随着其他的 JPEG 标记

exif数据解析

如果图片图片是16进制数据,如下:

FF D8  FF E0 00 10 4A 46 49 46 00 01 02 01 00 60 00 60   00 00  FF E1 08 32 45 78 69 66 00 00 49 49 10 60 00 60   20 00 …… FFD9  

那么FF D8为SOI标志位,FF E0为exif文件起始位,后面四位 为exif marker信息的长度。取这个长度的数据解析为TIFFdata数据,exif直接解析为字符串貌似也没有问题。 

FF D8

FF E000 10 4A 46 49 46 00 01 02 01 00 60 00 60   00 00        mark0,00 10 =16位

    FF E108 32 45 78 69 66 00 00 49 49 10 60 00 60   20 00 …… mark1,00 10 =2098位

    …… 

   Image stream

FFD9


每个段都是由FFxx开头,其中xx是段的标识,接着就是就是两位的端长度。后面跟着的就是数据。前面的元数据外读取完成后,后面的二进制数据就是图片数据。

数据大小描述符(2个字节) 是 "Motorola" 的字节顺序, 数据的低位被存放在高地址,也就是 BigEndian. 请注意上面中的 "数据内容" 中包含他前面的数据大小描述符, 如果下面的是一个标记的话;

这个长度的表示方法是按照高位在前,低位在后的,与 Intel 的表示方法不同。比方说一个段的长度是0x12AB,那么它会按照0x12,0xAB的顺序存储。但是如果按照Intel的方式:高位在后,低位在前的方式会存储成0xAB,0x12,而这样的存储方法对于JPEG是不对的。这样的话如果一个程序不认识JPEG文件某个段,它就可以读取后两个字节,得到这个段的长度,并跳过忽略它。

关于exif信息解码,请阅读《JPEG/Exif/TIFF格式解读(2):图片元数据保存及EXIF详解

jpeg10中必须的段类型

这里列举10种必备的段类型

APP0图像识别信息

-------------------------------------------------

名称      字节数   值                   说明

-------------------------------------------------

段标识       1     FF

段类型       1     E0

段长度       2     0010               如果有RGB缩略图就=16+3n

  (以下为段内容)

交换格式      5    4A46494600          “JFIF”的ASCII码

主版本号      1

次版本号      1  

密度单位      1    0=无单位;1=点数/英寸;2=点数/厘米

X像素密度     2                         水平方向的密度   

Y像素密度     2                         垂直方向的密度

缩略图X像素   1                         缩略图水平像素数目  

缩略图Y像素   1                         缩略图垂直像素数目

(如果“缩略图X像素”和“缩略图Y像素”的值均>0,那么才有下面的数据)

RGB缩略图     3×n  n=缩略图像素总数=缩略图X像素×缩略图Y像素

说明:

JFIF是JPEG File Interchange Forma的缩写,即JPEG文件交换格式,另外还有TIFF等格式,很少用

“如果有RGB缩略图就=16+3n”是什么意思呢?比如说“缩略图X像素”和“缩略图Y像素”的值均为48,就表示有一个48×48像素的缩略图(n=48×48),缩略图是24位真彩位图,用3个字节来表示一个像素,所以共占用3n个字节。但大多数JPG文件都没有这个“鸡肋”缩略图。

DQT定义量化表

--------------------------------------------------------------------------

名称  字节数 值       说明

--------------------------------------------------------------------------

段标识   1     FF

段类型   1     DB

段长度   2     43      其值=3+n(当只有一个QT时)

(以下为段内容)

QT信息  1     0-3位:QT号

4-7位:QT精度(0=8bit,1字节;否则=16bit,2字节)

QT        n             n=64×QT精度的字节数

说明:

JPEG文件一般有2个DQT段,为Y值(亮度)定义1个, 为C值(色度)定义1个。

一个DQT段可以包含多个QT, 每个都有自己的信息字节

参考资料:

图片文件Exif信息详细说明 blog.sina.com.cn/s/blog_651251e60102uz3d.html#AboutExif

图像Exif信息 元数据(Metadata) https://www.jianshu.com/p/a6d67df60e7e

关于图片文件旋转JPEG与EXIF信息 https://blog.csdn.net/yulimin/article/details/102827865

https://www.media.mit.edu/pia/Research/deepview/exif.html

https://baike.baidu.com/item/Exif/422825?fr=aladdin

读取JPG图片的Exif属性(一) - Exif信息简介 https://blog.csdn.net/fioletfly/article/details/53605959

读取JPG图片的Exif属性(二) - C代码实现 https://blog.csdn.net/fioletfly/article/details/54094940

读取JPG图片的Exif属性(三) - Exif属性读取GPS信息代码(C/C++实现)https://blog.csdn.net/fioletfly/article/details/54133422

在jpg图片添加Exif信息的C程序实现https://blog.csdn.net/psy6653/article/details/79658144

JPEG添加EXIF https://blog.csdn.net/weixin_43549602/article/details/84654965

jpeg图片格式详解 https://blog.csdn.net/yun_hen/article/details/78135122

压缩算法——JPEG2000 编解码原理 https://blog.csdn.net/ytang_/article/details/76571635

PNG、JPEG、BMP等几种图片格式详解 https://www.jianshu.com/p/f5557c0e689e

使用HTTP2和渐进式JPEG图片更快的加载图像 https://www.zcfy.cc/article/performance-calendar-raquo-even-faster-images-using-http2-and-progressive-jpegs-2216.html

转载本站文章《JPEG/Exif/TIFF格式解读(1):JEPG图片压缩与存储原理分析》,

请注明出处:https://www.zhoulujun.cn/html/theory/multimedia/CG-CV-IP/8396.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345