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

Posted zls366

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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循环中可以选择图片合成的方案一或者二。

 

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

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中

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:图片拆分

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  记录耗时,写入

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

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获取

用Python+PIL将多个jpg图像批量合并成一个pdf文件

一、引言

在《用Python+PIL将目录下jpg图像批量转成pdf文件》介绍了将一个目录下所有的jpg文件批量转成一对一的pdf文件的方法,但单位后来又要求将所有图片合并到一个PDF中看,在实际工作中,确实有时还需要将批量图片文件合并生成一个PDF文件,本文就简单介绍一下该方法。

二、实现方法

要将多张图片合并到一个PDF文件,还是用上文介绍的PIL库,只是不是逐一将图像文件存储为PDF文件,而是将图像文件打开后的对象实例添加到一个列表中,最后在一起存储到PDF中即可。

需要注意的是,存储是必须通过图像对象的save方法,因此将所有文件都装入后,需要从链表中将第一个文件取下作为调用save方法的对象实例,同时要从链表中删除取下的对象。

这样方法处理的PDF文件,其图像在PDF中的顺序与装载到链表中的顺序一致。

三、完整代码

下面的代码既将每个图片文件生成对应的PDF文件,同时还生成一个将所有图片都合并到一个PDF文件的合并文件:

from PIL import Image
import os


def jpg2pdf(jpgFile): 
    global imglist

    path,fileName = jpgFile.rsplit('\\\\',1)
    preName,postName = fileName.rsplit('.',1)


    img = Image.open(jpgFile)
    imglist.append(img)
    return img.save(path+"\\\\"+preName+'.pdf', "PDF", resolution=100.0, save_all=True)


def jpg2pdfByPath(pathName):
    global imglist
  
    imglist = []
    imgfile = ''
    files = os.listdir(pathName)
    for f in files:
        if f.lower().find(".jpg")>0 :

            jpg2pdf(pathName+'\\\\'+f)
            imgfile = f
         

    imgMerge = imglist.pop(0) #取出第一个图片示例

    imgMerge.save(pathName+r'\\merge.pdf',"PDF", resolution=100.0, save_all=True, append_images=imglist)
    print("all images processed!")


jpg2pdfByPath(r'F:\\temp\\pic')

四、小结

本文介绍了使用Python+PIL库将多个图片合并成一个PDF文件的方法。

写博不易,敬请支持:

如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!

如对文章内容存在疑问,可在博客评论区留言,或通过扫描博客左边的二维码关注:老猿Python 微信公号发消息咨询。

关于老猿的付费专栏

  1. 付费专栏《 使用PyQt开发图形界面Python应用》专门介绍基于Python的PyQt图形界面开发基础教程,对应文章目录为《 使用PyQt开发图形界面Python应用专栏目录》;

  2. 付费专栏《 moviepy音视频开发专栏 )详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,对应文章目录为《moviepy音视频开发专栏文章目录》;

  3. 付费专栏《 OpenCV-Python初学者疑难问题集》为《 OpenCV-Python图形图像处理 》的伴生专栏,是笔者对OpenCV-Python图形图像处理学习中遇到的一些问题个人感悟的整合,相关资料基本上都是老猿反复研究的成果,有助于OpenCV-Python初学者比较深入地理解OpenCV,对应文章目录为《 OpenCV-Python初学者疑难问题集专栏目录

  4. 付费专栏《Python爬虫入门 》站在一个互联网前端开发小白的角度介绍爬虫开发应知应会内容,包括爬虫入门的基础知识,以及爬取CSDN文章信息、博主信息、给文章点赞、评论等实战内容。

前两个专栏都适合有一定Python基础但无相关知识的小白读者学习,第三个专栏请大家结合《 OpenCV-Python图形图像处理 》的学习使用。

对于缺乏Python基础的同仁,可以通过老猿的免费专栏《 专栏:Python基础教程目录》从零开始学习Python。

如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

老猿Python,跟老猿学Python!

☞ ░ 前往老猿Python博文目录

以上是关于C# 将多个图片合并成TIFF文件的两种方法(续集)的主要内容,如果未能解决你的问题,请参考以下文章

用Python+PIL将多个jpg图像批量合并成一个pdf文件

Python将两个字典合并成一个字典

把C#程序(含多个Dll)合并成一个Exe的超简单方法

怎样将多个txt文档快速合并成一个文档?

用C语言怎么把文件中的两行合并成一行

C# 实现将多个word文档合并成一个word文档的功能