Files
ImageCompressor/Compressor/FilesManager.cs
2026-02-02 21:56:45 +08:00

425 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}