01
—
preface
Why add a sequel after the name? Because a man wrote an article before. I continued to develop and explore on his basis. The link to his article:
C # two methods of merging multiple pictures into TIFF files
At the end of the article, the man raised a leftover question:
The size of tif files generated by the two methods is quite different...
The size of 7 original drawings is 4.8M, and the first one is 1.36M,
The second direct 23.5M
Or maybe I haven't done a good job of compression....
Then I don't know.
-----This problem is actually caused by the difference between the two compression methods and compression ratio
02
—
Noun interpretation
First of all, we should clarify several nouns:
① Image compression quality: refers to the definition of the compressed image. Generally speaking, when saving the image with image processing software, in order to save hard disk space, the original image is generally compressed through a certain algorithm, and the compression quality is essentially the size of the compression ratio. The higher the compression ratio, the less hard disk space the generated image occupies, The worse the picture quality; On the contrary, the lower the compression ratio, the higher the image quality, but the larger the hard disk space.
② TIFF: Tag Image File Format (TIFF) is a flexible bitmap format. TIFF (Tag Image File Format) image file is one of the commonly used formats in graphics and image processing. Its image format is very complex, but due to its flexible storage of image information, it can support many color systems and is independent of the operating system, Therefore, it has been widely used. TIFF files have a. tif extension.
03
—
New exploration
Change 1: the brother's processing method is to load each picture into the memory with the CompressionImage method for encoding and compression. In fact, this step is unnecessary. It not only wastes time, but also saves space. Because the third party called itself has the function of picture compression, so my project is removed in this section;
Change 2: this guy compresses a group of pictures into one tiff at a time. My application scenario is to compress one picture after another, so each one is compressed;
Change 3: in addition to image synthesis, I added the method of splitting tiff files in my project;
Change 4: record the time of loading, synthesizing and saving pictures and write them to the log file
04
—
Source sharing
I use the console for the test here. After running, enter the value: the number of pictures to be merged can be executed. In the test process, I only have one picture, so I cloned this picture:
Program.cs: there are three methods, Main, BmpToTiff and CopyImage. CopyImage is responsible for image cloning. Scheme 1 or 2 of image synthesis can be selected in the for loop of BmpToTiff method.
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 \ new folder";
CopyImage(filePath);
BmpToTiff(filePath);
//SplitTiffToBMP.SplitTiff(filePath);// Image segmentation
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++) { //Call scenario 1 //var timeSpan = BitMiracle.Jpeg2Tiff(i, imagePaths.Length, filePath, $".tif", 100); //Call scenario 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("Total time taken to compose pictures:" + stopwatch.ElapsedMilliseconds.ToString() + "millisecond", true); Console.WriteLine("Total time taken to compose pictures:" + stopwatch.ElapsedMilliseconds.ToString() + "millisecond"); } public static void CopyImage(string filePath) { Console.WriteLine("Please enter the number of merged pictures:"); //Console input number of pictures bool flag = false; int count = 0; while (!flag) { flag = int.TryParse(Console.ReadLine(), out count); } //Copy picture Bitmap bitmap = new Bitmap(filePath + "0.bmp"); for (int i = 1; i < count; i++) { bitmap.Save(filePath + $".bmp"); } bitmap.Dispose(); } }
}
I wrote the code of combining and splitting into the class library 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
{
///Merge jpg
///
///bitmap array
///Save path
///Picture quality, 1-100
///
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 image synthesis, one at a time /// </summary> ///< param name = "index" > picture ID < / param > ///< param name = "totalcount" > number of pictures < / param > ///< param name = "tiffsavepath" > TIFF save path < / param > ///< param name = "quality" > compression 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] .bmp")}; Stopwatch sw = new Stopwatch(); sw.Start(); Bitmap bmp = new Bitmap(tiffSavePath + $".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; } } } }
}
The parameter quality of Jpeg2Tiff can adjust the compression ratio. The default value is 75 and the range is [0100]. Of course, you can also use tif.SetField(TiffTag.COMPRESSION, Compression.NONE); There are the following options for setting the compression mode. Each mode adopts different algorithms, so the compression efficiency is different
picture
RasterEdge.cs scheme 2: insert the new image into the tiff synthesized last time
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 + $".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 + $".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 + $".tif"); sw.Stop(); timeSpan.SaveTime = sw.ElapsedMilliseconds.ToString(); sTotal.Stop(); timeSpan.TotalTime = sTotal.ElapsedMilliseconds.ToString(); timeSpan.flag = true; return timeSpan; } }
}
picture
SplitTiffToBMP.cs: picture split
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 timeSpans = new List();
//Split a Tif picture into multiple Gif pictures 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("Total time taken to split pictures:" + stopwatch.ElapsedMilliseconds.ToString() + "millisecond", true); Console.WriteLine("Total time taken to split pictures:" + stopwatch.ElapsedMilliseconds.ToString() + "millisecond"); } }
}
Data model:
FileWrite.cs record takes time, write
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TiffHelper
{
public class FileWrite
{
///
///log file printing
///
///File full path
///Write content
///true: append, false: overwrite
public static void WriteLogFile(string content, bool flag,string logFullPath = @ "C:\Users\majm\Desktop \ new folder \ 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($",,,,,");
return report.ToString();
}
}
}
05
—
Run demo
picture