您的位置:首页 > 其它

常见图片格式详解

2017-11-05 21:34 295 查看
标明原作者信息 http://www.cnblogs.com/xiangism
做了几年有关图形、图像的工作,对图片格式算是小有经验,在此写成一文章总结下。虽然一开始并不想讲很理论的东西,但写完后发现几乎全是理论,细想一下关于图片格式的知识本身就是理论的东西,囧~~ 那就力求用最简单的方式将这些“理论”讲清楚吧。

常见的图片格式有bmp, jpg(jpeg), png, gif, webp等。

图像基本数据结构

要讲图片格式还先得从图像的基本数据结构说起。在计算机中, 图像是由一个个像素点组成,像素点就是颜色点,而颜色最简单的方式就是用RGB或RGBA表示, 如图所示

Bmp结构体
由于bmp格式比较简单,本人已实现了一份简单的c++代码,具有读取、保存bmp图片的功能,只支持24位的bmp格式。

代码在 http://git.oschina.net/xiangism/blogData 的“常见图片格式详解/ImageDemo/BmpDemo”文件夹中。

虽然这里只建立了vs2008项目,但代码在linux, mac平台下都可以编译通过。

需要说明的是为了统一处理,将bmp读取到LBitmap::m_pixel中时就将其转化为32位从上往下排列的图像格式了。并且会有y坐标的转化。
所以在读取的时候会有一个temp_line先存储文件中的24位数据,再转化为32位数据。在保存时也是先将32位数据转化到temp_line的24位数据上,然后再写入文件。(如果仅仅是处理bmp,那么这么多的一个A通道是冗余数据,但后面处理png图片时就会用到这个A通道)

如果用上面的代码来读取如图所示的图片(放大8倍后的显示图):

JpegAPI
具体的实现在JpegDemo
用上面的函数进行jpeg的读取和保存的测试

1     class Program
2     {
3         static string src_path;
4         static long small_size = 0;
5
6         private static ImageCodecInfo GetCodecInfo(string mimeType)
7         {
8             ImageCodecInfo[] CodecInfo = ImageCodecInfo.GetImageEncoders();
9
10             foreach (ImageCodecInfo ici in CodecInfo) {
11                 if (ici.MimeType == mimeType)
12                     return ici;
13             }
14             return null;
15         }
16
17         static void SaveImage(string path)
18         {
19             FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
20
21             if (stream.Length == 0) {
22                 stream.Close();
23                 return;
24             }
25
26             byte[] file_data = new byte[stream.Length];
27             stream.Read(file_data, 0, (int)stream.Length);
28             Stream mem = new MemoryStream(file_data);
29
30             long old_size = stream.Length;
31
32             try {
33                 Image img = new Bitmap(mem);
34                 stream.Close();
35
36                 EncoderParameter p = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 80L);
37                 EncoderParameters ps = new EncoderParameters(1);
38
39                 ps.Param[0] = p;
40
41                 img.Save(path, GetCodecInfo("image/jpeg"), ps);
42
43                 FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read);
44                 long new_size = f.Length;
45                 f.Close();
46
47                 small_size += old_size - new_size;
48
49             } catch (System.Exception ex) {
50
51             } finally {
52                 stream.Close();
53             }
54
55         }
56
57         static void ConvertOneImage(string path, bool is_save)
58         {
59             if (is_save) {
60                 string new_name = src_path + path.Substring(path.LastIndexOf('\\'));
61                 File.Copy(path, new_name, true);
62             }
63
64             SaveImage(path);
65         }
66
67         static void ShowSize(string path)
68         {
69             FileStream stream = new FileStream(path, FileMode.Open);
70             Image img = Image.FromStream(stream, false, false);
71             stream.Close();
72             //Console.WriteLine("{0} {1}", img.Width, img.Height);
73             if (img.Width == 0) {
74                 Console.WriteLine("Error");
75             }
76         }
77
78         static void BatchJpeg()
79         {
80             string path = Application.ExecutablePath;
81             path = path.Substring(0, path.LastIndexOf('\\'));
82
83             src_path = path + "\\" + "src";
84             //Console.WriteLine(src_path);
85
86             Console.WriteLine("批量转化jpeg图片,保证其图片质量的前提下减少其存储大小");
87             Console.WriteLine("若想保存原图片,其按y(原图将放在src文件夹下), 否则按任意键开始处理");
88
89             ConsoleKeyInfo key = Console.ReadKey();
90             bool is_save = false;
91
92             if (key.KeyChar == 'y' || key.KeyChar == 'Y') {
93                 is_save = true;
94
95                 Directory.CreateDirectory("src");
96             }
97
98             string[] files = Directory.GetFiles(path, "*.jpg");
99
100             Stopwatch sw = new Stopwatch();
101             sw.Start();
102
103             for (int i = 0; i < files.Length; ++i) {
104                 ConvertOneImage(files[i], is_save);
105                 //string s = files[i].Substring(files[i].LastIndexOf('\\') + 1);
106                 //Console.WriteLine((i + 1).ToString() + "/" + files.Length.ToString() + " \t " + s);
107
108                 //ShowSize(files[i]);
109             }
110
111             sw.Stop();
112
113             Console.WriteLine("*********已结束,按任意键结束********");
114             double v = (small_size * 1.0 / (1024 * 1024));
115
116             Console.WriteLine("共减少 " + v.ToString("0.00##") + "M 的存储空间");
117             Console.WriteLine("耗时:" + sw.Elapsed.TotalSeconds.ToString("0.00##") + "秒");
118             Console.ReadKey();
119         }
120
121         static void Main(string[] args)
122         {
123             BatchJpeg();
124             //SaveImage("E:\\img - 副本.JPG");
125             //string path = "E:\\cpp_app\\LiteTools\\JpegBatch\\bin\\Release\\img - 副本.JPG";
126             //File.Delete(path);
127         }
128     }


Exif信息

另外jpeg文件一般有一个附属的exif信息,这个信息中有图像大小,拍摄时间,拍摄的相关参数,照片方向,图像缩略图等信息。

用相机拍出来的jpeg都会有这个信息。如果照片方向不是正立的话,在读取到像素取后,还得按exif所指明的方向将图像旋转下。mspaint程序就没有做这个处理,有些图片用picasa查看和用mspaint查看方向就不一样。当然为了简单起见,上面的LBitmap中也自动忽略了exif信息及其图像拍摄时的方向。

如果不用读取1/2,1/4,1/8的方法,也可以从exif中来读取缩略图,但这个缩略图一般很小。

说到exif,不得不说一款用perl实现的命令行工具:exiftool。几乎所有的多媒体文件(图像、音乐、视频)都可以用这个工具来查看其有关信息,当然如果不是jpeg文件就是指广义上的"exif"。在git中有已经编译好可执行文件exiftool.exe。使用方法是将这个文件放到系统路径下,然后在想查看的文件路径下执行 exiftool filename

在实现BatchJpeg工具时如果仅仅用上面实现的LBitmap来读取,保存, 将会失去exif信息, 而相片的拍摄时间等信息又很重要, 所以还得用另一个库exiv2来读取写入exif。如果用c#, 用上面的代码exif信息会自动保留下来。默默地向c#致敬。

intelJpeg库

如果在win32环境下对jpeg IO速度有很高的要求,可以使用interlJpeg库,不开源,但提供有*.h,*.lib文件。这个库可以大大提高jpg读取、保存速度。

当时分别用c#和c实现了jpeg批量转化工具, 在处理大量图片时发现c#用时居然只有c的一半。太奇怪了,按理说,c的速度比c#应该快才对啊, 而实事是c慢了这么多。 最后发现问题就在libjpeg上,用了intetJpeg后速度就和c#差不多了(猜想.NET内部也是用intelJpeg来处理jpeg)。

PNG格式

png是一种无损压缩格式, 压缩大概是用行程编码算法。

png可以有透明效果。

png比较适合适量图,几何图。 比如本文中出现的这些图都是用png保存,比用joeg保存体积要小。

再强调一下: jpeg比较适合存储色彩“杂乱”的拍摄图片,png比较适合存储几何特征强的图形类图片。

png可能有24位图和32位图之分。32位图就是带有alpha通道的图片。
将图片a绘制到另一幅图片b上,如果图片a没有alpha通道,那么就会完全将b图片的像素给替换掉。而如果有alpha通道,那么最后覆盖的结果值将是c = a*alpha + b*(1-alpha)
再对LBitmap添加png的支持。
添加接口如下:

static bool ReadPngSize(const wchar_t *path, int *width, int *height);
static bool IsPngFile(const wchar_t *filename);
bool ReadPng(const wchar_t *filename);
bool WritePng(const wchar_t *filename);


具体实现在PngDemo中。有调用libpng库,并且libpng库依赖zlib库(由此可以看出png算法有用到常规的压缩算法)。

GIF格式

上面提到的bmp,jpeg,png图片都只有一帧,而gif可以保存多帧图像,如图所示



libgif库可以用来读取gif图片。gif中有个参数可以控制图片变化的快慢。在程序中可以使用这个参数,也可以自己定义一个参数,这就是为什么gif图片,在不同程序中查看时其变化速度不一样。

webp

google开发的一种有损、透明图片格式,相当于jpeg和png的合体,google声称其可以把图片大小减少40%。

一个强大的格式库,CxImage

CxImage几乎可以读取任何图片格式

下面是其头文件中的宏定义:

#define CXIMAGE_SUPPORT_WINDOWS 1
#define CXIMAGE_SUPPORT_EXIF    1
#define CXIMAGE_SUPPORT_BMP 1
#define CXIMAGE_SUPPORT_GIF 1
#define CXIMAGE_SUPPORT_JPG 1
#define CXIMAGE_SUPPORT_PNG 1
#define CXIMAGE_SUPPORT_ICO 1
#define CXIMAGE_SUPPORT_TIF 1
#define CXIMAGE_SUPPORT_TGA 1
#define CXIMAGE_SUPPORT_PCX 1
#define CXIMAGE_SUPPORT_WBMP 1
#define CXIMAGE_SUPPORT_WMF 1

#define CXIMAGE_SUPPORT_JP2 1
#define CXIMAGE_SUPPORT_JPC 1
#define CXIMAGE_SUPPORT_PGX 1
#define CXIMAGE_SUPPORT_PNM 1
#define CXIMAGE_SUPPORT_RAS 1

#define CXIMAGE_SUPPORT_MNG 1
#define CXIMAGE_SUPPORT_SKA 1
#define CXIMAGE_SUPPORT_RAW 1
#define CXIMAGE_SUPPORT_PSD 1


CxImage在针对特定格式时,也是调用了其它图片库(比如libjpeg, libpng, libtiff)。由于CxImage太过庞大,如果不想使用其全部代码,可以自己从中“偷取”特定图片格式的读取、保存代码。

版权声明:本文来自xiangism的博客。 仅供参考、学习使用。若需转载或引用本文中的方法,请标明原作者信息 http://www.cnblogs.com/xiangism 。商业用途请联系博主
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: