Init
This commit is contained in:
88
Compressor/CountProgress.cs
Normal file
88
Compressor/CountProgress.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ImageCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// 用来总的记录各个线程处理照片的进度,以及标记线程运行结果
|
||||
/// </summary>
|
||||
class CountProgress
|
||||
{
|
||||
ImageCompressorForm owner;
|
||||
public CountProgress(int totalCount, ImageCompressorForm owner)
|
||||
{
|
||||
this.TotalCount = totalCount;
|
||||
this.owner = owner;
|
||||
FailedInfos = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
public ReaderWriterLock RWLock = new ReaderWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// 总的处理了的照片数量
|
||||
/// </summary>
|
||||
public int ProcessedCount => SuccessCount + FailedCount + SkipCount;
|
||||
|
||||
/// <summary>
|
||||
/// 处理过成功的照片数量
|
||||
/// </summary>
|
||||
public int SuccessCount { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 处理过失败的照片数量
|
||||
/// </summary>
|
||||
public int FailedCount { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 已经跳过的照片数量
|
||||
/// </summary>
|
||||
public int SkipCount { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 处理失败的信息
|
||||
/// </summary>
|
||||
public List<string> FailedInfos { get; set; }
|
||||
/// <summary>
|
||||
/// 需要处理的照片总数
|
||||
/// </summary>
|
||||
public int TotalCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经处理完所有照片
|
||||
/// </summary>
|
||||
public bool Finished => ProcessedCount == TotalCount;
|
||||
|
||||
/// <summary>
|
||||
/// 处理前照片的大小总和
|
||||
/// </summary>
|
||||
public long PreSizeKBSum { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 压缩后照片的大小总和
|
||||
/// </summary>
|
||||
public long PostSizeKBSum { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// 计时器
|
||||
/// </summary>
|
||||
public Stopwatch stopwatch { get; set; } = new Stopwatch();
|
||||
|
||||
/// <summary>
|
||||
/// Update Data To Winform
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
owner.SetProgressValue(ProcessedCount);
|
||||
if (Finished)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
owner.ShowReport();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
29
Compressor/ImgPaths.cs
Normal file
29
Compressor/ImgPaths.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ImageCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// 一张照片的打开路径和储存路径
|
||||
/// </summary>
|
||||
class ImgPaths
|
||||
{
|
||||
public bool KeepOriginal { get; set; } = true;
|
||||
public FileInfo OriginalFileInfo { get; private set; }
|
||||
public FileInfo DuplicatedFileInfo { get; private set; }
|
||||
public ImgPaths(string readPath, string writePath)
|
||||
{
|
||||
OriginalFileInfo = new FileInfo(readPath);
|
||||
string extension = Path.GetExtension(writePath).ToLower();
|
||||
if(extension != ".jpg" || extension !=".jpeg")
|
||||
{
|
||||
Path.ChangeExtension(writePath, ".jpg");
|
||||
}
|
||||
this.DuplicatedFileInfo = new FileInfo(writePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
239
Compressor/ImgsProcess.cs
Normal file
239
Compressor/ImgsProcess.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
//#define NoOutput
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using System.Dynamic;
|
||||
|
||||
namespace ImageCompressor
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 一个单独的线程,来批量压缩图片
|
||||
/// </summary>
|
||||
class ImgsProcess
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 这个压缩进程内的所有压缩图片的路径
|
||||
/// </summary>
|
||||
ImgPaths[] ImgPaths { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 计数当前线程编号,用于Debug
|
||||
/// </summary>
|
||||
public int threadIndex=-1;
|
||||
|
||||
/// <summary>
|
||||
/// 引用的进度总控
|
||||
/// </summary>
|
||||
readonly CountProgress progress;
|
||||
|
||||
int currentIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 设定是否要在过程中停止计算,标记为停止后,等待计算停止
|
||||
/// </summary>
|
||||
public bool ToStop { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否所有计算已经停止
|
||||
/// </summary>
|
||||
public bool Stoped { get; private set; } = false;
|
||||
/// <summary>
|
||||
/// 该线程处理的所有图片数量
|
||||
/// </summary>
|
||||
public int AllCount { get; private set; }
|
||||
|
||||
public ImgsProcess(int index, List<ImgPaths> imgPaths, CountProgress countProgress)
|
||||
{
|
||||
threadIndex = index;
|
||||
ImgPaths = imgPaths.ToArray();
|
||||
AllCount = ImgPaths.Length;
|
||||
this.progress = countProgress;
|
||||
}
|
||||
/// <summary>
|
||||
/// 开始时设定所有的压缩参数
|
||||
/// </summary>
|
||||
/// <param name="compressLevel"></param>
|
||||
/// <param name="maxWidth"></param>
|
||||
/// <param name="maxHeight"></param>
|
||||
public void SetCompressLevel(int compressLevel,int maxWidth, int maxHeight)
|
||||
{
|
||||
this.compressLevel = compressLevel;
|
||||
this.maxWidth = maxWidth;
|
||||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
int compressLevel;
|
||||
int maxWidth;
|
||||
int maxHeight;
|
||||
|
||||
/// <summary>
|
||||
/// 用来传递每个Img压缩时候的数据
|
||||
/// </summary>
|
||||
class DataPack
|
||||
{
|
||||
public DataPack(ImgPaths paths, JPGCompressor compressor)
|
||||
{
|
||||
this.compressor = compressor;
|
||||
this.imgPaths = paths;
|
||||
}
|
||||
public JPGCompressor compressor;
|
||||
public ImgPaths imgPaths;
|
||||
public byte[] readBytes = null;
|
||||
public byte[] saveBytes = null;
|
||||
}
|
||||
#if DEBUG
|
||||
|
||||
double Tick => Math.Round(DateTime.Now.TimeOfDay.TotalSeconds % 1000, 3);
|
||||
#endif
|
||||
public void Start()
|
||||
{
|
||||
Stoped = false;
|
||||
ToStop = false;
|
||||
ResetCountData();
|
||||
while (currentIndex < AllCount)
|
||||
{
|
||||
#if DEBUG
|
||||
long t1 = progress.stopwatch.ElapsedMilliseconds;
|
||||
#endif
|
||||
DataPack dataPack = new DataPack(ImgPaths[currentIndex],new JPGCompressor(compressLevel,maxWidth,maxHeight));
|
||||
ReadOneImage(dataPack);
|
||||
CompressCurrent(dataPack);
|
||||
WriteOneImage(dataPack);
|
||||
#if DEBUG
|
||||
long t2 = progress.stopwatch.ElapsedMilliseconds;
|
||||
#endif
|
||||
progress.RWLock.AcquireWriterLock(-1);
|
||||
UpdateCountProgress();
|
||||
ResetCountData();
|
||||
Thread.Sleep(25);
|
||||
progress.RWLock.ReleaseWriterLock();
|
||||
#if DEBUG
|
||||
|
||||
Debug.WriteLine($"thread {threadIndex} count {currentIndex}, from {t1}, used {t2 - t1}");
|
||||
#endif
|
||||
currentIndex++;
|
||||
if (ToStop)
|
||||
{
|
||||
Stoped = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Stoped = true;
|
||||
}
|
||||
|
||||
//当前线程运行的结果变量
|
||||
int successCount = 0;
|
||||
int failedCount = 0;
|
||||
List<string> failedPaths = new List<string>();
|
||||
int skipCount = 0;
|
||||
long preSizeKB = 0;
|
||||
long saveSizeKB = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 重置当前线程运行的结果变量
|
||||
/// </summary>
|
||||
void ResetCountData()
|
||||
{
|
||||
successCount = 0;
|
||||
failedCount = 0;
|
||||
failedPaths.Clear();
|
||||
skipCount = 0;
|
||||
preSizeKB = 0;
|
||||
saveSizeKB = 0;
|
||||
}
|
||||
/// <summary>
|
||||
/// 读照片到DataPack
|
||||
/// </summary>
|
||||
/// <param name="dataPack"></param>
|
||||
void ReadOneImage(DataPack dataPack)
|
||||
{
|
||||
ImgPaths imgPaths = dataPack.imgPaths;
|
||||
if(!imgPaths.OriginalFileInfo.Exists)
|
||||
{
|
||||
failedCount++;
|
||||
failedPaths.Add(Localize.Get("Read file missing") + imgPaths.OriginalFileInfo.FullName);
|
||||
return;
|
||||
}
|
||||
preSizeKB += imgPaths.OriginalFileInfo.Length / 1024;
|
||||
if (FilesManager.IsSkip(imgPaths.OriginalFileInfo))
|
||||
{
|
||||
skipCount++;
|
||||
return;
|
||||
}
|
||||
dataPack.readBytes = dataPack.compressor.ReadFileToBytes(imgPaths.OriginalFileInfo, out string error);
|
||||
if (dataPack.readBytes == null)
|
||||
{
|
||||
failedCount++;
|
||||
failedPaths.Add(error + imgPaths.OriginalFileInfo.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 压缩得到文件的Bytes
|
||||
/// </summary>
|
||||
/// <param name="dataPack"></param>
|
||||
void CompressCurrent(DataPack dataPack)
|
||||
{
|
||||
if (dataPack.readBytes != null)
|
||||
{
|
||||
dataPack.saveBytes = dataPack.compressor.CompressBytes(dataPack.readBytes,out string error);
|
||||
if(dataPack.saveBytes == null)
|
||||
{
|
||||
failedCount++;
|
||||
failedPaths.Add(error + dataPack.imgPaths.OriginalFileInfo.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写Bytes到文件
|
||||
/// </summary>
|
||||
/// <param name="dataPack"></param>
|
||||
void WriteOneImage(DataPack dataPack)
|
||||
{
|
||||
if (dataPack.saveBytes != null)
|
||||
{
|
||||
bool keepOriginal = dataPack.imgPaths.KeepOriginal;
|
||||
FileInfo saveFile = keepOriginal ? dataPack.imgPaths.DuplicatedFileInfo : dataPack.imgPaths.OriginalFileInfo;
|
||||
|
||||
bool re = dataPack.compressor.SaveCompressBytes(dataPack.saveBytes, saveFile, out string error);
|
||||
if (re)
|
||||
{
|
||||
saveSizeKB += dataPack.saveBytes.LongLength / 1024;
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
failedCount++;
|
||||
failedPaths.Add(error + dataPack.imgPaths.DuplicatedFileInfo.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将本线程结果更新到progress的结果
|
||||
/// </summary>
|
||||
void UpdateCountProgress()
|
||||
{
|
||||
if (progress.Finished) return;
|
||||
progress.SuccessCount += successCount;
|
||||
progress.FailedCount+= failedCount;
|
||||
progress.SkipCount += skipCount;
|
||||
progress.FailedInfos.AddRange(failedPaths);
|
||||
progress.PreSizeKBSum+= preSizeKB;
|
||||
progress.PostSizeKBSum += saveSizeKB;
|
||||
progress.Update();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
366
Compressor/JPGCompressor.cs
Normal file
366
Compressor/JPGCompressor.cs
Normal file
@@ -0,0 +1,366 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ImageCompressor
|
||||
{
|
||||
/// <summary>
|
||||
/// 单张JPG的压缩运算
|
||||
/// </summary>
|
||||
class JPGCompressor
|
||||
{
|
||||
int compressLevel;
|
||||
int MaxWidth;
|
||||
int MaxHeight;
|
||||
long Quality;
|
||||
|
||||
ImageCodecInfo jpegEncoder;
|
||||
EncoderParameters jpeParameters;
|
||||
|
||||
public JPGCompressor(int compressLevel, int maxWidth,int maxHeight)
|
||||
{
|
||||
CompressLevel= compressLevel;
|
||||
MaxWidth = maxWidth;
|
||||
MaxHeight = maxHeight;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 设定压缩质量
|
||||
/// </summary>
|
||||
public int CompressLevel
|
||||
{
|
||||
get { return compressLevel; }
|
||||
private set
|
||||
{
|
||||
compressLevel = value;
|
||||
Quality = compressLevel * 5 + 50; //(50-100)
|
||||
if (!SetJpegCompressor()) throw new Exception("Cannot Create JPG Comporessor");
|
||||
}
|
||||
}
|
||||
|
||||
public static int CompressionLevelToMaxWidth(int level)
|
||||
{
|
||||
return Math.Max((int)(1920 * (level * 0.2)), 480);//480-3840
|
||||
}
|
||||
public static int CompressionLevelToMaxHeight(int level)
|
||||
{
|
||||
return Math.Max((int)(1080 * (level * 0.2)), 360);//360-2160
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#region CompressBytes
|
||||
|
||||
/// <summary>
|
||||
/// 储存Bitmap到JPG文件。
|
||||
/// 之后释放 writeBitmap
|
||||
/// </summary>
|
||||
public bool SaveCompressBytes(byte[] saveBytes, FileInfo saveFile, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (saveFile.Exists)
|
||||
{
|
||||
if (saveFile.IsReadOnly)
|
||||
{
|
||||
errorMessage = Localize.Get("Save file is ReadOnly. ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DirectoryInfo directory = saveFile.Directory;
|
||||
if (!directory.Exists)
|
||||
{
|
||||
try
|
||||
{
|
||||
directory.Create();
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorMessage = Localize.Get("Cannot create save folder. ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(saveFile.FullName, saveBytes);
|
||||
return true;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is IOException)
|
||||
{
|
||||
errorMessage = Localize.Get("Save file is occupied. ");
|
||||
return false;
|
||||
}
|
||||
errorMessage = Localize.Get("Save file error at ")+$"{ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开一个Image文件,返回Bytes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte[] ReadFileToBytes(FileInfo readFile, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
if (!readFile.Exists)
|
||||
{
|
||||
errorMessage = Localize.Get("Read file not exist. ");
|
||||
return null;
|
||||
}
|
||||
if(readFile.Length > 1024 * 1024 * 256)
|
||||
{
|
||||
errorMessage = Localize.Get("Read image larger than 256 MB");
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
return File.ReadAllBytes(readFile.FullName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = Localize.Get("Read file error at ") + $"{ex.Message}";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] CompressBytes(byte[] readBytes, out string errorMessage)
|
||||
{
|
||||
MemoryStream memoryStream = null;
|
||||
MemoryStream saveMemoryStream = null;
|
||||
Bitmap rawBit = null;
|
||||
Graphics graphics = null;
|
||||
Bitmap newBit = null;
|
||||
|
||||
errorMessage = null;
|
||||
try
|
||||
{
|
||||
memoryStream = new MemoryStream(readBytes, false);
|
||||
saveMemoryStream = new MemoryStream();
|
||||
rawBit = new Bitmap(memoryStream);
|
||||
memoryStream.Close();
|
||||
memoryStream.Dispose();
|
||||
double percentW = (double)MaxWidth / rawBit.Width;
|
||||
double percentH = (double)MaxHeight / rawBit.Height;
|
||||
double percent = Math.Min(percentW, percentH);
|
||||
percent = Math.Min(1, percent);
|
||||
if (percent < 1 || compressLevel < 10)
|
||||
{
|
||||
int newW = (int)(percent * rawBit.Width);
|
||||
int newH = (int)(percent * rawBit.Height);
|
||||
newBit = new Bitmap(newW, newH);
|
||||
graphics = Graphics.FromImage(newBit);
|
||||
|
||||
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
|
||||
graphics.DrawImage(rawBit, 0, 0, newW, newH);
|
||||
graphics.Dispose();
|
||||
rawBit.Dispose();
|
||||
newBit.Save(saveMemoryStream, jpegEncoder, jpeParameters);
|
||||
newBit.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Level为10则直接储存JPG
|
||||
rawBit.Save(saveMemoryStream, jpegEncoder, jpeParameters);
|
||||
}
|
||||
int length = (int)saveMemoryStream.Length;
|
||||
saveMemoryStream.Position = 0;
|
||||
byte[] savedBytes = new byte[length];
|
||||
saveMemoryStream.Read(savedBytes, 0, length);
|
||||
saveMemoryStream.Close();
|
||||
saveMemoryStream.Dispose();
|
||||
return savedBytes;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = Localize.Get("Save file error at ")+$"{ex.Message}";
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
memoryStream?.Close();
|
||||
memoryStream?.Dispose();
|
||||
saveMemoryStream?.Close();
|
||||
saveMemoryStream?.Dispose();
|
||||
rawBit?.Dispose();
|
||||
newBit?.Dispose();
|
||||
graphics?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CompressBitmap
|
||||
/// <summary>
|
||||
/// 储存Bitmap到JPG文件。
|
||||
/// </summary>
|
||||
/// <param name="bitmap"></param>
|
||||
/// <param name="savePath"></param>
|
||||
/// <param name="savedSizeInKB"></param>
|
||||
public bool SaveCompressBitmap(Bitmap bitmap, FileInfo saveFile, out long savedSizeInKB, out string errorMessage)
|
||||
{
|
||||
|
||||
savedSizeInKB = 0;
|
||||
errorMessage = null;
|
||||
if (saveFile.Exists)
|
||||
{
|
||||
if (saveFile.IsReadOnly)
|
||||
{
|
||||
errorMessage = Localize.Get("Save file is ReadOnly. ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DirectoryInfo directory = saveFile.Directory;
|
||||
if (!directory.Exists)
|
||||
{
|
||||
try
|
||||
{
|
||||
directory.Create();
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorMessage = Localize.Get("Cannot create save folder. ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = saveFile.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
|
||||
bitmap.Save(stream, jpegEncoder, jpeParameters);
|
||||
savedSizeInKB = stream.Length / 1024;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is IOException)
|
||||
{
|
||||
errorMessage = Localize.Get("Save file is occupied. ");
|
||||
return false;
|
||||
}
|
||||
errorMessage = Localize.Get("Save file error at ") + $"{ex.Message}";
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream?.Close();
|
||||
stream?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开一个Image文件
|
||||
/// </summary>
|
||||
/// <param name="readFile"></param>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="readSizeInKB"></param>
|
||||
/// <param name="errorMessage"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadFileToImage(FileInfo readFile, out Bitmap image, out long readSizeInKB, out string errorMessage)
|
||||
{
|
||||
image = null;
|
||||
readSizeInKB = 0;
|
||||
errorMessage = null;
|
||||
|
||||
if (!readFile.Exists)
|
||||
{
|
||||
errorMessage = Localize.Get("Read file not exist. ");
|
||||
return false;
|
||||
}
|
||||
FileStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = readFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
Image im = Image.FromStream(stream);
|
||||
image = new Bitmap(im);
|
||||
im.Dispose();
|
||||
readSizeInKB = stream.Length / 1024;
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
readSizeInKB = readFile.Length / 1024;
|
||||
errorMessage = Localize.Get("Save file error at ") + $"{ex.Message}";
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream?.Close();
|
||||
stream?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将图片压缩,不释放Image
|
||||
/// </summary>
|
||||
/// <param name="raw"></param>
|
||||
/// <returns></returns>
|
||||
public Bitmap CompressImgToBitmap(Bitmap raw)
|
||||
{
|
||||
|
||||
double percentW = (double)MaxWidth / raw.Width;
|
||||
double percentH = (double)MaxHeight / raw.Height;
|
||||
double percent = Math.Min(percentW, percentH);
|
||||
percent = Math.Min(1, percent);
|
||||
if (percent < 1 || compressLevel < 10)
|
||||
{
|
||||
int newW = (int)(percent * raw.Width);
|
||||
int newH = (int)(percent * raw.Height);
|
||||
Bitmap newBit = new Bitmap(newW, newH);
|
||||
using (Graphics g = Graphics.FromImage(newBit))
|
||||
{
|
||||
|
||||
//g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
|
||||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
|
||||
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
|
||||
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
|
||||
g.DrawImage(raw, 0, 0, newW, newH);
|
||||
return newBit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Bitmap bitmap = new Bitmap(raw);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
bool SetJpegCompressor()
|
||||
{
|
||||
var temp = ImageCodecInfo.GetImageDecoders();
|
||||
jpegEncoder = temp.FirstOrDefault(x => x.MimeType == "image/jpeg");
|
||||
if (jpegEncoder == null) return false;
|
||||
|
||||
System.Drawing.Imaging.Encoder encoder = System.Drawing.Imaging.Encoder.Quality;
|
||||
jpeParameters = new EncoderParameters(1);
|
||||
EncoderParameter parameter = new EncoderParameter(encoder, Quality);
|
||||
jpeParameters.Param[0] = parameter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user