425 lines
13 KiB
C#
425 lines
13 KiB
C#
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
|
||
}
|
||
}
|