//#define InitialPath using System; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Windows.Forms; using System.IO; using System.Diagnostics; namespace ImageCompressor { public partial class ImageCompressorForm : Form { public ImageCompressorForm() { manager = new FilesManager(this); } /// /// 后台读取图片,并且负责运行 /// FilesManager manager; /// /// 替换原有的Panel作为进度条 /// NewProgressPanel newProgressPanel; protected override void OnLoad(EventArgs e) { this.newProgressPanel = new NewProgressPanel(new Font("Bahnschrift Condensed", 12F), SystemColors.ControlDark); InitializeComponent(); SetLanguage(); newProgressPanel.Clone(ProgressPanel); ProgressPanel.Visible = false; this.Controls.Add(this.newProgressPanel); OnSizeChanged(null); ZoomInPictureBox.Visible = false; PostPictureBox.MouseMove += OnPictureMouseMovePost; PreviousPictureBox.MouseMove += OnPictureMouseMovePrevious; ZoomInPictureBox.MouseMove += OnZoomMouseMove; PostPictureBox.MouseWheel += OnZoomMouseWheelPost; PreviousPictureBox.MouseWheel += OnZoomMouseWheelPre; MaxWidthPixelTextBox.Leave += MaxWidthPixelTextBox_FocusLeave; MaxHeightPixelTextBox.Leave += MaxHeightPixelTextBox_FocusLeave; SkipFileSizeTextBox.Leave += SkipFileSizeTextBox_FocusLeave; MaxWidthPixelTextBox.KeyDown += TextBox_KeyDown; MaxHeightPixelTextBox.KeyDown += TextBox_KeyDown; SkipFileSizeTextBox.KeyDown += TextBox_KeyDown; ApplySettingsFromForm(); UpdateDisplayInfo(); UpdateRunButtonText(); SetProgressValue(0); PathTextBox.DragDrop += Box_DragDrop; PathTextBox.DragEnter += Box_DragEnter; this.DragDrop += Box_DragDrop; this.DragEnter += Box_DragEnter; #if InitialPath && DEBUG //string path = @"C:\Users\wucl12\Downloads\Test\Italy"; string path = @"D:\Users\MandO\Downloads\重庆照片"; dragDropFolderPaths.Add(path); UpdateDropFilePaths(); UpdateRunButtonText(); #endif } /// /// 启动时设置语言 /// void SetLanguage() { if(!Localize.IsEN) { this.PathTextBox.Font = new System.Drawing.Font("等线", 12F); this.SizeEstimateLabel.Font = new Font("等线", 12F); this.KeepOriginalCheckBox.Font = new Font("等线", 12F); this.MaxPixelMultiplyLabel.Font = new Font("等线", 12F); this.MaxPixelLimitLabel.Font = new Font("等线", 12F); //this.CompressLevelTextLabel.Font = new Font("等线", 15F); this.TitleTextLabel.Font = new Font("等线", 12F); this.InfoLabel.Font = new Font("等线", 12F); this.RunButton.Font = new Font("等线", 12F, FontStyle.Bold); this.SubFolderCheckBox.Font = new Font("等线", 12F); this.SkipFileSizeLabel.Font = new Font("等线", 12F); this.CompressLevelLabel.Font = new Font("等线", 12F); } this.CompressLevelLabel.Text = Localize.Get("Compress Level (1-10)"); this.SkipFileSizeLabel.Text = Localize.Get("Skip File Size (KB)"); this.KeepOriginalCheckBox.Text = Localize.Get("Keep Original (Compress in new folder)"); this.SubFolderCheckBox.Text = Localize.Get("Include SubFolders"); this.MaxPixelLimitLabel.Text = Localize.Get("Max Pixel Limit"); this.TitleTextLabel.Text = Localize.Get("Image Compressor") +" 2026.02.02 by WR"; this.Text = Localize.Get("Image Compressor"); } #region Resize /// /// 点击到左右边界 /// ClickResize clickResizeHorizen = ClickResize.None; /// /// 点击到上下边界 /// ClickResize clickResizeVertical = ClickResize.None; /// /// 记录点击边界值 /// enum ClickResize { None, Left, Right, Top, Buttom, } /// /// 鼠标点击位置是移动还是缩放 /// enum SizeChangeState { None, ChangeSize, Move, } /// /// 记录移动或者缩放状态 /// SizeChangeState sizeChangeState = SizeChangeState.None; /// /// 绘制尺寸时的间距控制常数 /// const int sizePad = 15; /// /// 减少卡顿 /// protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ExStyle |= 0x02000000; return cp; } } /// /// 动态缩放 /// /// protected override void OnSizeChanged(EventArgs e) { SuspendLayout(); this.CloseButton.Left = Width - sizePad - CloseButton.Width; this.MaxmizeButton.Left = CloseButton.Left - sizePad - MaxmizeButton.Width; this.MinimizeButton.Left = MaxmizeButton.Left - sizePad - MinimizeButton.Width; this.PathTextBox.Width = Width - sizePad - PathTextBox.Left; this.RunButton.Left = Width / 2 + sizePad / 2; this.newProgressPanel.Top = Height - sizePad - newProgressPanel.Height; this.newProgressPanel.Left = RunButton.Left; this.newProgressPanel.Width = Width - sizePad - RunButton.Left; this.ProgressPanel.Location = newProgressPanel.Location; this.ProgressPanel.Width = newProgressPanel.Width; this.RunButton.Top = this.ProgressPanel.Top - sizePad - RunButton.Height; this.RunButton.Width = Width - sizePad - RunButton.Left; this.InfoLabel.Left = RunButton.Left; this.InfoLabel.Top = this.RunButton.Top - sizePad - InfoLabel.Height; this.SizeEstimateLabel.Left = this.RunButton.Left; this.SizeEstimateLabel.Top = this.InfoLabel.Top - sizePad - this.SizeEstimateLabel.Height; this.PostPictureBox.Left = RunButton.Left; this.PostPictureBox.Width = this.RunButton.Width; this.PostPictureBox.Height = this.SizeEstimateLabel.Top - sizePad - this.PostPictureBox.Top; this.PreviousPictureBox.Height = this.PostPictureBox.Height; this.PreviousPictureBox.Width = this.PostPictureBox.Left- sizePad - this.PreviousPictureBox.Left; this.KeepOriginalCheckBox.Top = ProgressPanel.Top; this.SubFolderCheckBox.Top = this.KeepOriginalCheckBox.Top - 10 - this.SubFolderCheckBox.Height; this.SkipFileSizeTextBox.Top = this.RunButton.Top - this.SkipFileSizeTextBox.Height; this.CompressLevelDownButton.Top = this.PostPictureBox.Bottom + sizePad-5; this.CompressLevelUpButton.Top = this.CompressLevelDownButton.Top; this.CompressLevelTextLabel.Top = this.CompressLevelUpButton.Top + 7; this.CompressLevelLabel.Top = this.CompressLevelTextLabel.Bottom - this.CompressLevelLabel.Height-3; this.SkipFileSizeTextBox.Top = this.CompressLevelUpButton.Bottom + sizePad-5; this.SkipFileSizeLabel.Top = this.SkipFileSizeTextBox.Top+5; this.MaxPixelLimitLabel.Top = this.CompressLevelLabel.Top; this.MaxPixelMultiplyLabel.Top = this.MaxPixelLimitLabel.Top; this.MaxWidthPixelTextBox.Top = this.CompressLevelUpButton.Top + 3; this.MaxHeightPixelTextBox.Top = this.MaxWidthPixelTextBox.Top; ResumeLayout(false); PerformLayout(); } /// /// 点击四边拖拽更改窗口大小的距离距离范围 /// const float ChangeSizeClickTolerance = 10; bool ClickNear(float n1, float n2) { return Math.Abs(n1 - n2) < ChangeSizeClickTolerance; } Point moveLocation; protected override void OnMouseDown(MouseEventArgs e) { this.RunButton.Focus(); sizeChangeState = SizeChangeState.None; if(e.Y > ChangeSizeClickTolerance && e.Y < MinimizeButton.Bottom && e.X < MinimizeButton.Left) { sizeChangeState = SizeChangeState.Move; moveLocation = e.Location; return; } if (ClickNear(0, e.X)) { clickResizeHorizen = ClickResize.Left; sizeChangeState = SizeChangeState.ChangeSize; } else if (ClickNear(Bounds.Width, e.X)) { clickResizeHorizen = ClickResize.Right; sizeChangeState = SizeChangeState.ChangeSize; } else clickResizeHorizen = ClickResize.None; if (ClickNear(0, e.Y)) { clickResizeVertical = ClickResize.Top; sizeChangeState = SizeChangeState.ChangeSize; } else if (ClickNear(Bounds.Height, e.Y)) { clickResizeVertical = ClickResize.Buttom; sizeChangeState = SizeChangeState.ChangeSize; } else clickResizeVertical = ClickResize.None; if (sizeChangeState == SizeChangeState.None) { base.OnMouseDown(e); } } protected override void OnMouseMove(MouseEventArgs e) { ZoomInPictureBox.Visible = false; if (sizeChangeState == SizeChangeState.Move) { if(this.WindowState != FormWindowState.Normal) { this.WindowState = FormWindowState.Normal; moveLocation = new Point(Width / 2, 10); } else { Size size = Bounds.Size; Point location = new Point(Bounds.X + e.X - moveLocation.X, Bounds.Y + e.Y - moveLocation.Y); Bounds = new Rectangle(location, size); } } else if(sizeChangeState == SizeChangeState.ChangeSize) { this.WindowState = FormWindowState.Normal; Point location = Bounds.Location; Size size = Bounds.Size; bool changed = false; if (clickResizeHorizen == ClickResize.Left) { location.X = Bounds.X + e.X; size.Width = Bounds.Width - e.X; changed = true; } else if (clickResizeHorizen == ClickResize.Right) { size.Width = e.X; changed = true; } if (clickResizeVertical == ClickResize.Top) { location.Y = Bounds.Y + e.Y; size.Height = Bounds.Height - e.Y; changed = true; } else if (clickResizeVertical == ClickResize.Buttom) { size.Height = e.Y; changed = true; } if (changed) { Bounds = new Rectangle(location, size); } } else { bool left = ClickNear(0, e.X); bool onHorizon = left || ClickNear(Bounds.Width, e.X); bool top = ClickNear(0, e.Y); bool onVertical = top || ClickNear(Bounds.Height, e.Y); if (onHorizon && onVertical) { if ((left && top) || ((!left) || (!top))) this.Cursor = Cursors.SizeNWSE; else this.Cursor = Cursors.SizeNESW; } else if (onHorizon) { this.Cursor = Cursors.SizeWE; } else if (onVertical) { this.Cursor = Cursors.SizeNS; } else this.Cursor = Cursors.Default; base.OnMouseMove(e); } } protected override void OnMouseUp(MouseEventArgs e) { if (sizeChangeState!= SizeChangeState.None) { clickResizeHorizen = ClickResize.None; clickResizeVertical= ClickResize.None; sizeChangeState = SizeChangeState.None; OnSizeChanged(null); } else base.OnMouseUp(e); } #endregion #region Drag&Drop List dragDropFolderPaths = new List(); /// /// 改变路径Box的消息 /// /// private void ChangePathTextBoxMessage(string messageText) { if (!string.IsNullOrEmpty(messageText)) { PathTextBox.Text = messageText; PathTextBox.ForeColor = Color.Gray; } } private void Box_DragEnter(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Link; } else e.Effect = DragDropEffects.None; } private void Box_DragDrop(object sender, DragEventArgs e) { dragDropFolderPaths.Clear(); Array files = (Array)e.Data.GetData(DataFormats.FileDrop); foreach (object file in files) { FileInfo info = new FileInfo(file.ToString()); if (info != null && info.Attributes.HasFlag(FileAttributes.Directory)) { dragDropFolderPaths.Add(info.FullName); } } if (dragDropFolderPaths.Count == 0) { ChangePathTextBoxMessage(Localize.Get("No Folders Droped")); return; } UpdateDropFilePaths(); UpdateRunButtonText(); } /// /// 当Drop时更新路径 /// void UpdateDropFilePaths() { manager.ReadAllFilePaths(dragDropFolderPaths); SetProgressValue(0); if (manager.FilesCount == 0) { MessageBox.Show(Localize.Get("No Images (JPG, PNG, BMP) Found in Folder Droped"), Localize.Get("Warning")); PostPictureBox.Image = null; PreviousPictureBox.Image = null; UpdateDisplayInfo(); return; } UpdateDisplayInfo(); DrawPreviewImg(); PathTextBox.Lines = dragDropFolderPaths.ToArray(); } #endregion #region Zoom Picture /// /// 滚轮缩放Zoom框 /// void OnZoomMouseWheelPre(object sender, MouseEventArgs e) { if (e.Delta > 0) ZoomFactor.ZoomPercent *= 0.8; else ZoomFactor.ZoomPercent *= 1.25; DrawZoomInImage(PreviousPictureBox, e); } void OnZoomMouseWheelPost(object sender, MouseEventArgs e) { if (e.Delta > 0) ZoomFactor.ZoomPercent *= 0.8; else ZoomFactor.ZoomPercent *= 1.25; DrawZoomInImage(PostPictureBox, e); } void OnZoomMouseMove(object sender, MouseEventArgs e) { ZoomInPictureBox.Location = new Point(ZoomInPictureBox.Left + e.X + 5, ZoomInPictureBox.Top - ZoomInPictureBox.Height + e.Y - 5); } void OnPictureMouseMovePost(object sender, MouseEventArgs e) { DrawZoomInImage(PostPictureBox, e); } void OnPictureMouseMovePrevious(object sender, MouseEventArgs e) { DrawZoomInImage(PreviousPictureBox, e); } long CpuTime => DateTime.Now.Ticks / 10000; long wait = 0; void DrawZoomInImage(PictureBox box, MouseEventArgs e) { long t = CpuTime; if (t - wait > 100) wait = t; else return; if (PreviousPictureBox.Image == null || PostPictureBox.Image == null) { ZoomInPictureBox.Visible = false; return; } ZoomInPictureBox.Visible = true; ZoomInPictureBox.Location = new Point(box.Left + e.X + 5, box.Top + e.Y - 155); ZoomFactor zoomFactor = new ZoomFactor(box.Bounds, e.Location, box.Image.Width, box.Image.Height); ZoomInPictureBox.Image = GetZoomImage(zoomFactor); } Image GetZoomImage(ZoomFactor factor) { Bitmap newBit = new Bitmap(300, 150); Rectangle destRectPre = new Rectangle(0, 0, 150, 150); Rectangle destRectPost = new Rectangle(150, 0, 150, 150); Graphics graphics = Graphics.FromImage(newBit); graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; graphics.SetClip(destRectPre); Rectangle srcRectPre = factor.GetSourceImage(PreviousPictureBox.Image); graphics.DrawImage(PreviousPictureBox.Image, destRectPre, srcRectPre, GraphicsUnit.Pixel); graphics.SetClip(destRectPost); Rectangle srcRectPost = factor.GetSourceImage(PostPictureBox.Image); graphics.DrawImage(PostPictureBox.Image, destRectPost, srcRectPost, GraphicsUnit.Pixel); graphics.Dispose(); //Debug.WriteLine("srcRectPre = {0}, srcRectPost = {1}", srcRectPre, srcRectPost); return newBit; } #endregion #region UI Event /// /// 防止内部修改文字的时候,调用了TextChange事件 /// bool internalUpdating = false; /// /// 在Drop文件之后,初始化Info显示 /// private void UpdateDisplayInfo() { internalUpdating = true; CompressLevelTextLabel.Text = manager.CompressLevel.ToString(); MaxHeightPixelTextBox.Text = manager.MaxHeight.ToString(); MaxWidthPixelTextBox.Text = manager.MaxWidth.ToString(); int fileCount = manager.FilesCount; if (fileCount > 0) { newProgressPanel.Text = $"0/{fileCount}"; newProgressPanel.Refresh(); manager.SetSkipFileSizeInKB(SkipFileSizeTextBox.Text); InfoLabel.Text = string.Format(Localize.Get("Find {0} Imgs , Sum Size = {1}"), manager.FilesCount, manager.GetFileSizeString(manager.PreAllImageSizeinKB)); } else { ChangePathTextBoxMessage(Localize.Get("Drag and Drop Folders Here To Start")); newProgressPanel.Text = $"0/0"; newProgressPanel.Refresh(); SizeEstimateLabel.Text = Localize.Get("New Image Size Estimate: ") + "0KB / 0KB"; InfoLabel.Text = ""; } internalUpdating = false; } /// /// 将Form的设置运用到后台程序中去 /// void ApplySettingsFromForm(bool updateWidthHeight = false) { manager.SetSkipFileSizeInKB(SkipFileSizeTextBox.Text); manager.IncludeSubFolders = SubFolderCheckBox.Checked; manager.KeepOriginal = KeepOriginalCheckBox.Checked; manager.CompressLevel = int.Parse(CompressLevelTextLabel.Text); if(updateWidthHeight) { manager.MaxWidth = int.Parse(MaxWidthPixelTextBox.Text); manager.MaxHeight = int.Parse(MaxHeightPixelTextBox.Text); } } /// /// 根据Seed绘制一个压缩后的预览图 /// void DrawPreviewImg() { if (!manager.GetRandomIndex(seed, out int index)) { //manager没有文件 PreviousPictureBox.Image = null; PostPictureBox.Image = null; SizeEstimateLabel.Text = Localize.Get("New Image Size Estimate: ") + "0KB / 0KB"; return; } var rawImage = manager.GetImageAtIndex(index, out string rawFileSize); PreviousPictureBox.Image = rawImage; var newImage = manager.GetCompressedImgAtIndex(index, out string newFileSize, out bool skip, out string errorMessage); if (skip) { PostPictureBox.Image = rawImage; SizeEstimateLabel.Text = Localize.Get("New Image Size Estimate: ")+$"{rawFileSize} / {rawFileSize}" + Localize.Get("(Skiped)"); } else { if (newImage == null) { SizeEstimateLabel.Text = errorMessage; PostPictureBox.Image = null; return; } PostPictureBox.Image = newImage; SizeEstimateLabel.Text = Localize.Get("New Image Size Estimate: ") +$"{newFileSize} / {rawFileSize}"; } ZoomFactor.ZoomPercent = 0.1; } void SkipFileSizeTextBox_FocusLeave(object sender, EventArgs e) { if (!int.TryParse(SkipFileSizeTextBox.Text, out _)) SkipFileSizeTextBox.Text = manager.CurrentSkipFileSizeInKB.ToString(); ApplySettingsFromForm(); } private void SubFolderCheckBox_CheckedChanged(object sender, EventArgs e) { ApplySettingsFromForm(); } private void KeepOriginalCheckBox_CheckedChanged(object sender, EventArgs e) { ApplySettingsFromForm(); } int seed = 0; private void PreviousPictureBox_Click(object sender, EventArgs e) { if (manager.FilesCount > 0) { seed++; DrawPreviewImg(); } else seed = 0; } private void AfterPictureBox_Click(object sender, EventArgs e) { if (manager.FilesCount > 0) { seed--; DrawPreviewImg(); } else seed = 0; } private void CompressLevelTextChanged(bool manuallWidthHeight=false) { ApplySettingsFromForm(manuallWidthHeight); DrawPreviewImg(); UpdateDisplayInfo(); } void MaxHeightPixelTextBox_FocusLeave(object sender, EventArgs e) { if (internalUpdating) return; if (!int.TryParse(MaxHeightPixelTextBox.Text, out _)) { MaxHeightPixelTextBox.Text = manager.MaxHeight.ToString(); } CompressLevelTextChanged(true); } private void TextBox_KeyDown(object sender, KeyEventArgs e) { if(e.KeyCode == Keys.Enter) { this.RunButton.Focus(); } } void MaxWidthPixelTextBox_FocusLeave(object sender, EventArgs e) { if (internalUpdating) return; if (!int.TryParse(MaxWidthPixelTextBox.Text, out _)) { MaxWidthPixelTextBox.Text = manager.MaxWidth.ToString(); } CompressLevelTextChanged(true); } private void CompressLevelDownButton_Click(object sender, EventArgs e) { if (manager.CompressLevel > 1) { this.CompressLevelTextLabel.Text = (manager.CompressLevel - 1).ToString(); CompressLevelTextChanged(); } } private void CompressLevelUpButton_Click(object sender, EventArgs e) { if (manager.CompressLevel < 10) { this.CompressLevelTextLabel.Text = (manager.CompressLevel + 1).ToString(); CompressLevelTextChanged(); } } private void CloseButton_Click(object sender, EventArgs e) { if (manager.State != RunningState.Running) { this.Close(); return; } else { //发送结束指令 manager.Stop(); while (true) { if (manager.State != RunningState.Running) { //确保运行结束后才关闭 this.Close(); return; } else Thread.Sleep(100); } } } private void MaxmizeButton_Click(object sender, EventArgs e) { if (this.WindowState != FormWindowState.Maximized) this.WindowState = FormWindowState.Maximized; else this.WindowState = FormWindowState.Normal; } private void MinimizeButton_Click(object sender, EventArgs e) { this.WindowState = FormWindowState.Minimized; } #endregion #region Run /// /// 用于内部线程调用 /// /// public delegate void SetProgressValueDelegate(int percent); /// /// 设定进度条 /// /// public void SetProgressValue(int fileCount) { if (InvokeRequired) { //从其他线程调用,则在此返回Form线程调用 SetProgressValueDelegate setProgressValueDelegate = new SetProgressValueDelegate(SetProgressValue); this.Invoke(setProgressValueDelegate, fileCount); } else { newProgressPanel.ProgressValue =(double)fileCount / manager.FilesCount; this.newProgressPanel.Text = $"{fileCount} / {manager.FilesCount}"; newProgressPanel.Refresh(); } } /// /// 根据RunningState更新按钮文字 /// public void UpdateRunButtonText() { switch(manager.State) { case RunningState.NoFile: RunButton.Text = Localize.Get("Waiting for image folders"); break; case RunningState.Running: RunButton.Text = Localize.Get("Running, Click to Pause"); break; case RunningState.WaitStop: RunButton.Text = Localize.Get("Running, Wait to Pause"); break; case RunningState.AllThreadStoped: RunButton.Text = Localize.Get("Paused, Click to Continue"); break; case RunningState.Finished: RunButton.Text = Localize.Get("Done"); break; case RunningState.Ready: RunButton.Text = Localize.Get("Ready"); break; default: RunButton.Text = Localize.Get("Run"); break; } } /// /// 点击Run之后运行 /// /// /// void RunButton_Click(object sender, EventArgs e) { if (manager.State == RunningState.Running) { manager.Stop(); } else if (manager.State == RunningState.Ready || manager.State == RunningState.AllThreadStoped) { manager.RunAllCompress(); } else //(manager.State == RunningState.Finished) { RunButton.Text = Localize.Get("Done !!!"); } UpdateRunButtonText(); } /// /// 运行完后显示结束报告 /// internal void ShowReport() { if (InvokeRequired) { //从其他线程调用,则在此返回Form线程调用 Action ac = new Action(ShowReport); this.Invoke(ac); } else { CountProgress progress = manager.progress; int minutes = (int)progress.stopwatch.Elapsed.TotalMinutes; float seconds = progress.stopwatch.Elapsed.Seconds + (progress.stopwatch.Elapsed.Milliseconds / 100) * 0.1f; string watchString; if (minutes > 0) watchString = string.Format(Localize.Get("{0}min {1}s"),minutes,seconds); else watchString = string.Format(Localize.Get("{0}s"),seconds); UpdateRunButtonText(); InfoLabel.Text = string.Format(Localize.Get("Compressed {0}, Skiped {1}, Failed {2}, "), progress.SuccessCount, progress.SkipCount, progress.FailedCount) + string.Format(Localize.Get("Time {0}, Compressed Result {1} / {2}"),watchString,manager.GetFileSizeString(progress.PostSizeKBSum), manager.GetFileSizeString(progress.PreSizeKBSum)); if (progress.FailedInfos.Count > 0) { MessageBox.Show(string.Join("\n", progress.FailedInfos), Localize.Get("Failed Images Info")); } } } #endregion } }