C# 将多个图片合并成TIFF文件的两种方法(续集)

01

名词解释

 

首先要弄清楚几个名词:

① 图像的压缩品质:指的就是压缩后的图象清晰度,通俗地讲,就是用图像处理软件保存图像时,为了节省硬盘空间,一般是要对原始图像通过一定的算法进行压缩的,而压缩品质实质上就是压缩比率的大小,压缩的比率越高,生成的图像所占硬盘空间就越少,便是图片质量也就越差;相反,压缩比率越低,图像的品质越高,但是相应地占用硬盘空间也就越大。

② Tiff:标签图像文件格式(Tag Image File Format,TIFF)是一种灵活的位图格式,TIFF(Tag Image File Format)图像文件是图形图像处理中常用的格式之一,其图像格式很复杂,但由于它对图像信息的存放灵活多变,可以支持很多色彩系统,而且独立于操作系统,因此得到了广泛应用。TIFF文件以.tif为扩展名。

 

02

新的探索

 

变化一:这位仁兄的处理方式是把每张图片先用CompressionImage这个方法加载到内存进行编码压缩的,实际上这一步是没有必要的,不仅仅浪费了时间还没有节省空间,因为调用的第三方本身就带了图片压缩的功能,所以这一段我的项目去掉了;

变化二:这位仁兄处理是把一组图片一次压缩成一张tiff,我这边的应用场景是图片一张一张来,所以就是每来一张就压缩一张;

变化三:除了图片合成,我的项目中添加了tiff文件拆分的方法;

变化四:记录图片加载、合成、保存的时间并写入log文件.

03

源码分享

我这里测试采用的是控制台,运行后输入你也数值:要合并的图片的数量,就可以执行了,测试过程我只有一张图片,所以我将这张图片进行了克隆:

Program.cs:这里有三个方法,依次是Main、BmpToTiff和CopyImage,CopyImage负责图片克隆,BmpToTiff方法的for循环中可以选择图片合成的方案一或者二。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TiffHelper;
 
namespace TiffConsole
{
    class Program
    {
        private static Stopwatch stopwatch = new Stopwatch();
        static void Main(string[] args)
        {
            string filePath = @"C:\Users\majm\Desktop\新建文件夹\";
            CopyImage(filePath);
            BmpToTiff(filePath);
            //SplitTiffToBMP.SplitTiff(filePath);//图片分割
            Console.ReadKey();
        }
         
        public static void BmpToTiff(string filePath)
        {
            List<TiffHelper.TimeSpan> timeSpans = new List<TiffHelper.TimeSpan>();
            
            string[] imagePaths = System.IO.Directory.GetFiles(filePath, "*.bmp");
            stopwatch.Start();
            for (int i = 0; i < imagePaths.Length; i++)
            {
                //调用方案1
                //var timeSpan = BitMiracle.Jpeg2Tiff(i, imagePaths.Length, filePath,  $"{i}.tif", 100);
                //调用方案2
                var timeSpan = RasterEdge.TIFFDocument(i,filePath);
                timeSpans.Add(timeSpan);
            }
            stopwatch.Stop();
 
            FileWrite.WriteLogFile("Id,LoadTime,MergeTime,SaveTime,TotalTime,flag", true);
            timeSpans.ForEach(n => FileWrite.WriteLogFile(n.ToString(), true));
            FileWrite.WriteLogFile("合成图片总计耗时:" + stopwatch.ElapsedMilliseconds.ToString() + "毫秒", true);
            Console.WriteLine("合成图片总计耗时:" + stopwatch.ElapsedMilliseconds.ToString() + "毫秒");
        }
        public static void CopyImage(string filePath)
        {
            Console.WriteLine("请输入合并图片的数量:");
            //控制台输入图片数量
            bool flag = false;
            int count = 0;
            while (!flag)
            {
                flag = int.TryParse(Console.ReadLine(), out count);
            }
 
            //复制图片
            Bitmap bitmap = new Bitmap(filePath + "0.bmp");
            for (int i = 1; i < count; i++)
            {
                bitmap.Save(filePath + $"{i}.bmp");
            }
            bitmap.Dispose();
        }
    }
}

  

合图和拆分的代码我写到了类库TiffHelper中:

 

BitMiracle.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
using BitMiracle.LibTiff.Classic;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
 
namespace TiffHelper
{
    public class BitMiracle
    {
        /// 合并jpg
        /// </summary>
        /// <param name="bmps">bitmap数组</param>
        /// <param name="tiffSavePath">保存路径</param>
        /// <param name="quality">图片质量,1-100</param>
        /// <returns></returns>
        public static bool Jpegs2Tiff(Bitmap[] bmps, string tiffSavePath, int quality = 15)
        {
            try
            {
                MemoryStream ms = new MemoryStream();
                using (Tiff tif = Tiff.ClientOpen(@"in-memory", "w", ms, new TiffStream()))
                {
                    foreach (var bmp in bmps)//
                    {
                        byte[] raster = GetImageRasterBytes(bmp, PixelFormat.Format24bppRgb);
                        tif.SetField(TiffTag.IMAGEWIDTH, bmp.Width);
                        tif.SetField(TiffTag.IMAGELENGTH, bmp.Height);
                        tif.SetField(TiffTag.COMPRESSION, Compression.JPEG);
                        tif.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
                        tif.SetField(TiffTag.JPEGQUALITY, quality);
                        tif.SetField(TiffTag.ROWSPERSTRIP, bmp.Height);
 
 
                        tif.SetField(TiffTag.XRESOLUTION, 90);
                        tif.SetField(TiffTag.YRESOLUTION, 90);
 
 
                        tif.SetField(TiffTag.BITSPERSAMPLE, 8);
                        tif.SetField(TiffTag.SAMPLESPERPIXEL, 3);
 
 
                        tif.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
 
 
                        int stride = raster.Length / bmp.Height;
                        ConvertSamples(raster, bmp.Width, bmp.Height);
 
 
                        for (int i = 0, offset = 0; i < bmp.Height; i++)
                        {
                            tif.WriteScanline(raster, offset, i, 0);
                            offset += stride;
                        }
 
 
                        tif.WriteDirectory();
                    }
                    System.IO.FileStream fs = new FileStream(tiffSavePath, FileMode.Create);
 
                    ms.Seek(0, SeekOrigin.Begin);
                    fs.Write(ms.ToArray(), 0, (int)ms.Length);
                    fs.Close();
                    return true;
                }
            }
            catch (Exception ex)
            {
                return false;
            }
        }
        private static MemoryStream ms1 = new MemoryStream();
        private static Tiff tif = Tiff.ClientOpen(@"in-memory", "w", ms1, new TiffStream());
        /// <summary>
        /// tiff图片合成,一次合并一张
        /// </summary>
        /// <param name="index">图片id</param>
        /// <param name="totalCount">图片数量</param>
        /// <param name="tiffSavePath">tiff保存路径</param>
        /// <param name="quality">压缩品质</param>
        /// <returns></returns>
        public static TimeSpan Jpeg2Tiff(int index, int totalCount, string tiffSavePath,string fileName, int quality = 75)
        {
            try
            {
                string fileFullPath = Path.Combine(tiffSavePath, fileName);
                Stopwatch sTotal = new Stopwatch();
                sTotal.Start();
                TimeSpan timeSpan = new TimeSpan();
                timeSpan.Id = index;
                //Bitmap[] bmps = new Bitmap[1] {new Bitmap( @"C:\Users\zhuyr\Desktop\AutoFocus\" + $"{i}.bmp")};
                Stopwatch sw = new Stopwatch();
 
                sw.Start();
                Bitmap bmp = new Bitmap(tiffSavePath + $"{index}.bmp");
                sw.Stop();
                timeSpan.LoadTime = sw.ElapsedMilliseconds.ToString();
 
                sw = new Stopwatch();
                sw.Start();
                byte[] raster = GetImageRasterBytes(bmp, PixelFormat.Format24bppRgb);
                tif.SetField(TiffTag.IMAGEWIDTH, bmp.Width);
                tif.SetField(TiffTag.IMAGELENGTH, bmp.Height);
                tif.SetField(TiffTag.COMPRESSION, Compression.NONE);
                tif.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
                tif.SetField(TiffTag.JPEGQUALITY, quality);
                tif.SetField(TiffTag.ROWSPERSTRIP, bmp.Height);
 
 
                tif.SetField(TiffTag.XRESOLUTION, 90);
                tif.SetField(TiffTag.YRESOLUTION, 90);
 
 
                tif.SetField(TiffTag.BITSPERSAMPLE, 8);
                tif.SetField(TiffTag.SAMPLESPERPIXEL, 3);
 
 
                tif.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
 
 
                int stride = raster.Length / bmp.Height;
                ConvertSamples(raster, bmp.Width, bmp.Height);
 
 
                for (int i = 0, offset = 0; i < bmp.Height; i++)
                {
                    tif.WriteScanline(raster, offset, i, 0);
                    offset += stride;
                }
 
 
                tif.WriteDirectory();
                sw.Stop();
                timeSpan.MergeTime = sw.ElapsedMilliseconds.ToString();
 
                sw = new Stopwatch();
                sw.Start();
                if (totalCount == 1000)
                {
                    if (index % 10 == 9)
                    {
                        System.IO.FileStream fs = new FileStream(fileFullPath, FileMode.Create);
                        ms1.Seek(0, SeekOrigin.Begin);
                        fs.Write(ms1.ToArray(), 0, (int)ms1.Length);
                        fs.Close();
                        sw.Stop();
                    }
                }
                else
                {
                    System.IO.FileStream fs = new FileStream(fileFullPath, FileMode.Create);
                    ms1.Seek(0, SeekOrigin.Begin);
                    fs.Write(ms1.ToArray(), 0, (int)ms1.Length);
                    fs.Close();
                    sw.Stop();
                }
                timeSpan.SaveTime = sw.ElapsedMilliseconds.ToString();
 
                sTotal.Stop();
                timeSpan.TotalTime = sTotal.ElapsedMilliseconds.ToString();
                timeSpan.flag = true;
                return timeSpan;
 
            }
            catch (Exception ex)
            {
                return null;
            }
        }
 
        private static byte[] GetImageRasterBytes(Bitmap bmp, PixelFormat format)
        {
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            byte[] bits = null;
            try
            {
                BitmapData bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, format);
                bits = new byte[bmpdata.Stride * bmpdata.Height];
                System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, bits, 0, bits.Length);
                bmp.UnlockBits(bmpdata);
            }
            catch
            {
                return null;
            }
            return bits;
        }
        private static void ConvertSamples(byte[] data, int width, int height)
        {
            int stride = data.Length / height;
            const int samplesPerPixel = 3;
 
 
            for (int y = 0; y < height; y++)
            {
                int offset = stride * y;
                int strideEnd = offset + width * samplesPerPixel;
 
 
                for (int i = offset; i < strideEnd; i += samplesPerPixel)
                {
                    byte temp = data[i + 2];
                    data[i + 2] = data[i];
                    data[i] = temp;
                }
            }
        }
 
    }
}

  Jpeg2Tiff这个方法的参数quality可以调节压缩比率,这个值默认是75,范围是【0,100】,当然你也可以通过tif.SetField(TiffTag.COMPRESSION, Compression.NONE);设置压缩方式,会有以下这些可选项,每种方式都采用了不同算法,所以压缩效率都是不一样的.

 

 RasterEdge.cs  方案2,每次把新来的图片插入上一次合成的tiff中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using RasterEdge.Imaging.Basic;
using RasterEdge.XDoc.TIFF;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace TiffHelper
{
    public class RasterEdge
    {
        public static TimeSpan TIFFDocument(int index,string filePath)
        {
            Stopwatch sTotal = new Stopwatch();
            sTotal.Start();
            TimeSpan timeSpan = new TimeSpan();
            timeSpan.Id = index;
 
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Bitmap[] bmps = new Bitmap[1] { new Bitmap(filePath + $"{index}.bmp") };
            sw.Stop();
            timeSpan.LoadTime = sw.ElapsedMilliseconds.ToString();
 
            sw = new Stopwatch();
            sw.Start();
            //ImageOutputOption option = new ImageOutputOption() { Color = ColorType.Color, Compression = ImageCompress.CCITT };
            ImageOutputOption option = new ImageOutputOption() { Type = ImageType.JPEG, Color = ColorType.Color, Compression = ImageCompress.Uncompressed };
            TIFFDocument tifDoc_new = new TIFFDocument(bmps, option);
            TIFFDocument tifDoc_old;
            if (index > 0)
            {
                tifDoc_old = new TIFFDocument(filePath + $"{index - 1}.tif");
                BasePage page = tifDoc_new.GetPage(0);
                tifDoc_old.InsertPage(page, index);
            }
            else
            {
                tifDoc_old = tifDoc_new;
            }
            if (tifDoc_old == null)
                throw new Exception("Fail to construct TIFF Document");
            sw.Stop();
            timeSpan.MergeTime = sw.ElapsedMilliseconds.ToString();
 
            sw = new Stopwatch();
            sw.Start();
            tifDoc_old.Save(filePath + $"{index}.tif");
            sw.Stop();
            timeSpan.SaveTime = sw.ElapsedMilliseconds.ToString();
 
            sTotal.Stop();
            timeSpan.TotalTime = sTotal.ElapsedMilliseconds.ToString();
            timeSpan.flag = true;
            return timeSpan;
        }
    }
}

  

 

 SplitTiffToBMP.cs:图片拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace TiffHelper
{
    public class SplitTiffToBMP
    {
        private static Stopwatch stopwatch = new Stopwatch();
        public static void SplitTiff(string filePath)
        {
            stopwatch.Start();
            List<TimeSpan> timeSpans = new List<TimeSpan>();
 
            //分割Tif图片为多个Gif图片
            System.Drawing.Image img = System.Drawing.Image.FromFile(filePath+"9.tif");
            Guid guid = (Guid)img.FrameDimensionsList.GetValue(0);
            FrameDimension dimension = new FrameDimension(guid);
            int totalPage = img.GetFrameCount(dimension);
 
            for (int i = 0; i < totalPage; i++)
            {
                TimeSpan timeSpan = new TimeSpan();
                Stopwatch sw = new Stopwatch();
                sw.Start();
                img.SelectActiveFrame(dimension, i);
                img.Save(filePath + i + ".bmp", System.Drawing.Imaging.ImageFormat.Bmp);
                sw.Stop();
                timeSpan.Id = i;
                timeSpan.TotalTime = sw.ElapsedMilliseconds.ToString();
                timeSpans.Add(timeSpan);
            }
            stopwatch.Stop();
            FileWrite.WriteLogFile("Id,,,,TotalTime", true);
            timeSpans.ForEach(n => FileWrite.WriteLogFile(n.ToString(), true));
            FileWrite.WriteLogFile("拆分图片总计耗时:" + stopwatch.ElapsedMilliseconds.ToString() + "毫秒", true);
            Console.WriteLine("拆分图片总计耗时:" + stopwatch.ElapsedMilliseconds.ToString() + "毫秒");
        }
    }
}

  

数据模型:

FileWrite.cs  记录耗时,写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace TiffHelper
{
    public class FileWrite
    {
        /// <summary>
        /// log文件打印
        /// </summary>
        /// <param name="logFullPath">文件全路径</param>
        /// <param name="content">写入内容</param>
        /// <param name="flag">true:追加,false:覆盖</param>
        public static void WriteLogFile(string content, bool flag,string logFullPath = @"C:\Users\majm\Desktop\新建文件夹\log.txt")
        {
            StreamWriter sw = new StreamWriter(logFullPath ,flag);
            sw.WriteLine(content);
            sw.Close();
        }
    }
}

  TimeSpan.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace TiffHelper
{
    public class TimeSpan
    {
        public int Id;
        public string LoadTime;
        public string MergeTime;
        public string SaveTime;
        public string TotalTime;
        public bool flag;
        public override string ToString()
        {
            StringBuilder report = new StringBuilder();
            report.Append($"{Id},{LoadTime},{MergeTime},{SaveTime},{TotalTime},{flag}");
            return report.ToString();
        }
    }
}

  

04

运行演示

 

 

05

项目源码

 

链接:https://pan.baidu.com/s/1xw6iJkPg_5-P63QpLlX0kA 

提取码:点击在看后添加小编微信zls20210502获取

posted @   zls366  阅读(1019)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示