This commit is contained in:
2026-02-02 21:56:45 +08:00
commit 320550399d
24 changed files with 4551 additions and 0 deletions

424
Compressor/FilesManager.cs Normal file
View File

@@ -0,0 +1,424 @@
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
{
/// <summary>
/// 后台线程运行状态
/// </summary>
enum RunningState
{
NoFile,
Ready,
Running,
WaitStop,
AllThreadStoped,
Finished,
}
/// <summary>
/// 管理读取IMG文件并且负责Invoke压缩多线程
/// </summary>
class FilesManager
{
public bool IncludeSubFolders { get; set; } = true;
ImageCompressorForm owner { get; set; }
public FilesManager(ImageCompressorForm owner)
{
this.owner = owner;
}
#region Files
/// <summary>
/// 储存读取的所有照片的Size
/// </summary>
public long PreAllImageSizeinKB { get; private set; }
/// <summary>
/// 所有的照片的路径(打开路径和储存路径)
/// </summary>
List<ImgPaths> ImgPathList { get; set; } = new List<ImgPaths>();
/// <summary>
/// 总共有多少张照片
/// </summary>
public int FilesCount => ImgPathList.Count;
/// <summary>
/// 开始读取照片
/// 从输入的文件夹里,提取出所有的照片路径
/// </summary>
/// <param name="folders"></param>
public void ReadAllFilePaths(IEnumerable<string> 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;
}
/// <summary>
/// 设定跳过的文件大小
/// </summary>
/// <param name="textInKB"></param>
public void SetSkipFileSizeInKB(string textInKB)
{
if (int.TryParse(textInKB, out int result))
skipFileSize = result * 1024;
}
public int CurrentSkipFileSizeInKB => (int)(skipFileSize / 1024);
/// <summary>
/// 判断是否需要跳过该文件
/// </summary>
/// <param name="fileInfo"></param>
/// <returns></returns>
public static bool IsSkip(FileInfo fileInfo)
{
return fileInfo == null || fileInfo.Length < skipFileSize;
}
/// <summary>
/// 格式化显示大小
/// </summary>
/// <param name="sizeInKB"></param>
/// <returns></returns>
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
/// <summary>
/// 用于储存临时保存的预览压缩图片
/// </summary>
string TempImgPath = Path.Combine(Path.GetTempPath(), "temp.jpg");
/// <summary>
/// 返回当前所有路径的随机序号如果没有路径return false
/// </summary>
/// <param name="seed"></param>
/// <param name="index"></param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 将列表中Index序号的图像进行压缩
/// 储存到临时文件里去
/// 然后读取该临时图片作为输出
/// 失败返回null
/// skip时返回null,fileSize = null;
/// </summary>
/// <param name="index"></param>
/// <param name="fileSize"></param>
/// <param name="skip"></param>
/// <returns></returns>
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
/// <summary>
/// 储存照片时是否保留源文件
/// </summary>
public bool KeepOriginal { get; set; } = true;
int compressLevel = 8;
/// <summary>
/// 当前的压缩级数
/// </summary>
public int CompressLevel
{
get
{
return compressLevel;
}
set
{
compressLevel = value;
MaxWidth = JPGCompressor.CompressionLevelToMaxWidth(compressLevel);
MaxHeight = JPGCompressor.CompressionLevelToMaxHeight(compressLevel);
}
}
/// <summary>
/// 当前的最大宽度
/// </summary>
public int MaxWidth { get; set; }
/// <summary>
/// 当前的最大高度
/// </summary>
public int MaxHeight { get; set; }
/// <summary>
/// 向Winform标记运行状态
/// </summary>
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;
}
}
}
/// <summary>
/// 用来储存多线程运行过程数据和结果
/// </summary>
internal CountProgress progress;
/// <summary>
/// 所有线程运行数据
/// </summary>
ImgsProcess[] processList;
/// <summary>
/// 多线程常量增加貌似并没有加速很多猜测卡在JPG的IO读取上。
/// </summary>
public const int ThreadCount = 16;
/// <summary>
/// 正式压缩所有图片
/// 返回线程数
/// </summary>
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();
}
}
/// <summary>
/// 等待所有线程结束之后再弹出Report
/// </summary>
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();
}
/// <summary>
/// 停止所有计算。
/// 先设定ToStop状态
/// 然后等所有线程计算结束后再停止弹出Report
/// </summary>
public void Stop()
{
foreach (ImgsProcess process in processList)
{
if (process.ToStop) return;
process.ToStop = true;
}
owner.UpdateRunButtonText();
// 等待所有线程结束之后再弹出Report
new Thread(StopAsync).Start();
}
/// <summary>
/// 多线程分工方法
/// </summary>
/// <returns></returns>
private ImgsProcess[] DivideIntoMultiProcess()
{
List<ImgsProcess> imgsProcess = new List<ImgsProcess>();
List<List<ImgPaths>> splitImgPath = new List<List<ImgPaths>>();
for (int t = 0; t < ThreadCount; t++)
{
splitImgPath.Add(new List<ImgPaths>());
}
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
}
}