using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ImageCompressor { /// /// 后台线程运行状态 /// enum RunningState { NoFile, Ready, Running, WaitStop, AllThreadStoped, Finished, } /// /// 管理读取IMG文件,并且负责Invoke压缩多线程 /// class FilesManager { public bool IncludeSubFolders { get; set; } = true; ImageCompressorForm owner { get; set; } public FilesManager(ImageCompressorForm owner) { this.owner = owner; } #region Files /// /// 储存读取的所有照片的Size /// public long PreAllImageSizeinKB { get; private set; } /// /// 所有的照片的路径(打开路径和储存路径) /// List ImgPathList { get; set; } = new List(); /// /// 总共有多少张照片 /// public int FilesCount => ImgPathList.Count; /// /// 开始读取照片 /// 从输入的文件夹里,提取出所有的照片路径 /// /// public void ReadAllFilePaths(IEnumerable folders) { ImgPathList.Clear(); processList = null; PreAllImageSizeinKB = 0; foreach (string folderPath in folders) { string[] files; if (IncludeSubFolders) files = Directory.GetFiles(folderPath, "*.*", SearchOption.AllDirectories).Where(x => IsImg(x)).ToArray(); else files = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly).Where(x => IsImg(x)).ToArray(); foreach (string file in files) { PreAllImageSizeinKB += new FileInfo(file).Length / 1024; } DirectoryInfo info = new DirectoryInfo(folderPath); string newFolder = Path.Combine(info.Parent.FullName, info.Name + Localize.Get("_Compressed")); ImgPathList.AddRange(files.Select(x => new ImgPaths(x, x.Replace(folderPath, newFolder)))); } progress = new CountProgress(ImgPathList.Count, owner); } static long skipFileSize = 900 * 1024; bool IsImg(string fileName) { string extension = Path.GetExtension(fileName); if (Path.GetFileName(fileName).StartsWith("._")) return false; switch (extension.ToLower()) { case ".jpg": case ".png": case ".jpeg": case ".bmp": return true; } return false; } /// /// 设定跳过的文件大小 /// /// public void SetSkipFileSizeInKB(string textInKB) { if (int.TryParse(textInKB, out int result)) skipFileSize = result * 1024; } public int CurrentSkipFileSizeInKB => (int)(skipFileSize / 1024); /// /// 判断是否需要跳过该文件 /// /// /// public static bool IsSkip(FileInfo fileInfo) { return fileInfo == null || fileInfo.Length < skipFileSize; } /// /// 格式化显示大小 /// /// /// public string GetFileSizeString(long sizeInKB) { if (sizeInKB > 1048576) return string.Format("{0:f2}GB", sizeInKB / 1048576.0); else if (sizeInKB > 1024) return string.Format("{0:f2}MB", sizeInKB / 1024.0); else return string.Format("{0:f2}KB", sizeInKB); } #endregion #region Preview /// /// 用于储存临时保存的预览压缩图片 /// string TempImgPath = Path.Combine(Path.GetTempPath(), "temp.jpg"); /// /// 返回当前所有路径的随机序号,如果没有路径,return false /// /// /// /// public bool GetRandomIndex(int seed, out int index) { if (FilesCount > 0) { Random random = new Random(seed); index = random.Next(0, FilesCount); return true; } index = -1; return false; } public Image GetImageAtIndex(int index, out string fileSize) { FileInfo rawFile = ImgPathList[index].OriginalFileInfo; if (rawFile.Exists) { try { using (FileStream stream = rawFile.OpenRead()) { Image raw = Image.FromStream(stream); fileSize = GetFileSizeString(stream.Length / 1024); stream.Close(); return raw; } } catch (Exception ex) { fileSize = $"Cannot preview Image {rawFile.FullName}, {ex.Message}"; return null; } } else { fileSize = Localize.Get("Missing"); return null; } } /// /// 将列表中Index序号的图像进行压缩 /// 储存到临时文件里去 /// 然后读取该临时图片作为输出 /// 失败返回null /// skip时返回null,fileSize = null; /// /// /// /// /// public Image GetCompressedImgAtIndex(int index, out string fileSize, out bool skip, out string errorMessage) { fileSize = null; FileInfo rawFile = ImgPathList[index].OriginalFileInfo; skip = IsSkip(rawFile); if (skip) { errorMessage = null; return null; } JPGCompressor compressor = new JPGCompressor(this.CompressLevel, this.MaxWidth, this.MaxHeight); if (!compressor.ReadFileToImage(rawFile, out Bitmap image, out _, out errorMessage)) { return null; } Bitmap bitmap = compressor.CompressImgToBitmap(image); //image.Dispose(); if (compressor.SaveCompressBitmap(bitmap, new FileInfo(TempImgPath), out long saveSizeInKB, out errorMessage)) { fileSize = GetFileSizeString(saveSizeInKB); } else fileSize = Localize.Get("Estimate Fail"); return bitmap; } #endregion #region MultiThread /// /// 储存照片时是否保留源文件 /// public bool KeepOriginal { get; set; } = true; int compressLevel = 8; /// /// 当前的压缩级数 /// public int CompressLevel { get { return compressLevel; } set { compressLevel = value; MaxWidth = JPGCompressor.CompressionLevelToMaxWidth(compressLevel); MaxHeight = JPGCompressor.CompressionLevelToMaxHeight(compressLevel); } } /// /// 当前的最大宽度 /// public int MaxWidth { get; set; } /// /// 当前的最大高度 /// public int MaxHeight { get; set; } /// /// 向Winform标记运行状态 /// public RunningState State { get { if(FilesCount == 0) { return RunningState.NoFile; } else if (processList == null) { return RunningState.Ready; } else if (progress.Finished) return RunningState.Finished; else { foreach (ImgsProcess process in processList) { if (!process.Stoped) { if(process.ToStop)return RunningState.WaitStop; return RunningState.Running; } } return RunningState.AllThreadStoped; } } } /// /// 用来储存多线程运行过程数据和结果 /// internal CountProgress progress; /// /// 所有线程运行数据 /// ImgsProcess[] processList; /// /// 多线程常量,增加貌似并没有加速很多,猜测卡在JPG的IO读取上。 /// public const int ThreadCount = 16; /// /// 正式压缩所有图片 /// 返回线程数 /// public void RunAllCompress() { //运行后不能修改KeepOriginal,CompressLevel foreach (ImgPaths path in ImgPathList) path.KeepOriginal = KeepOriginal; //将所有的图片Path,平均分配到【线程数】个ProcessImgs里 if (State == RunningState.Ready) { progress.stopwatch.Restart(); processList = DivideIntoMultiProcess(); } else progress.stopwatch.Start(); //另一种情况是Stoped,就继续上次计算 foreach(ImgsProcess process in processList) { //一旦开始了运行,再修改的参数就不影响了 process.SetCompressLevel(this.CompressLevel, this.MaxWidth, this.MaxHeight); } Thread[] threads = processList.Select(x => new Thread(x.Start)).ToArray(); foreach (Thread thread in threads) { thread.Start(); } } /// /// 等待所有线程结束之后再弹出Report /// void StopAsync() { while (true) { int stopCount = 0; foreach (ImgsProcess process in processList) { if (process.Stoped) stopCount++; else Thread.Sleep(100); } if (stopCount == processList.Length) break; } progress.stopwatch.Stop(); owner.ShowReport(); } /// /// 停止所有计算。 /// 先设定ToStop状态 /// 然后等所有线程计算结束后再停止弹出Report /// public void Stop() { foreach (ImgsProcess process in processList) { if (process.ToStop) return; process.ToStop = true; } owner.UpdateRunButtonText(); // 等待所有线程结束之后再弹出Report new Thread(StopAsync).Start(); } /// /// 多线程分工方法 /// /// private ImgsProcess[] DivideIntoMultiProcess() { List imgsProcess = new List(); List> splitImgPath = new List>(); for (int t = 0; t < ThreadCount; t++) { splitImgPath.Add(new List()); } int count = ImgPathList.Count; for (int i = 0; i < count; i++) { int threadIndex = i % ThreadCount; splitImgPath[threadIndex].Add(ImgPathList[i]); } int index = 0; for (int t = 0; t < ThreadCount; t++) { if (splitImgPath[t].Count > 0) { imgsProcess.Add(new ImgsProcess(index++, splitImgPath[t], progress)); } } return imgsProcess.ToArray(); } #endregion } }