Init
This commit is contained in:
424
Compressor/FilesManager.cs
Normal file
424
Compressor/FilesManager.cs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user