diff --git a/MovieBarCodeGenerator/FfmpegWrapper.cs b/MovieBarCodeGenerator/FfmpegWrapper.cs index 790b35f..58ecd9a 100644 --- a/MovieBarCodeGenerator/FfmpegWrapper.cs +++ b/MovieBarCodeGenerator/FfmpegWrapper.cs @@ -102,49 +102,65 @@ IEnumerable GetLazyStream() { using (cancellationToken.Register(() => process.Kill())) { - foreach (var item in ReadBitmapStream(process.StandardOutput.BaseStream)) + foreach (var item in ReadBitmapStream(process.StandardOutput.BaseStream, cancellationToken)) { yield return item; } - cancellationToken.ThrowIfCancellationRequested(); } } return GetLazyStream(); } - private IEnumerable ReadBitmapStream(Stream stdout) + private IEnumerable ReadBitmapStream(Stream stdout, CancellationToken cancellationToken) { using (var reader = new BinaryReader(stdout)) { while (true) { - // https://en.wikipedia.org/wiki/BMP_file_format - var magicNumber = reader.ReadBytes(2); - if (magicNumber.Length != 2) + Bitmap bmp; + try { - break; + // https://en.wikipedia.org/wiki/BMP_file_format + var magicNumber = reader.ReadBytes(2); + if (magicNumber.Length != 2) + { + break; + } + + if (magicNumber[0] != 0x42 || magicNumber[1] != 0x4D) + { + throw new InvalidDataException(); + } + + var bmpSizeBytes = reader.ReadBytes(4); + var bmpSize = BitConverter.ToInt32(bmpSizeBytes, 0); + + var remainingDataLength = bmpSize - bmpSizeBytes.Length - magicNumber.Length; + var remainingData = reader.ReadBytes(remainingDataLength); + + var ms = new MemoryStream(); + ms.Write(magicNumber, 0, magicNumber.Length); + ms.Write(bmpSizeBytes, 0, bmpSizeBytes.Length); + ms.Write(remainingData, 0, remainingData.Length); + + // We can't just give it our input stream, + // because it would not stop at the end of the first image. + bmp = new Bitmap(ms); } - - if (magicNumber[0] != 0x42 || magicNumber[1] != 0x4D) + catch (Exception) { - throw new InvalidDataException(); + if (cancellationToken.IsCancellationRequested) + { + cancellationToken.ThrowIfCancellationRequested(); + yield break; + } + else + { + throw; + } } - var bmpSizeBytes = reader.ReadBytes(4); - var bmpSize = BitConverter.ToInt32(bmpSizeBytes, 0); - - var remainingDataLength = bmpSize - bmpSizeBytes.Length - magicNumber.Length; - var remainingData = reader.ReadBytes(remainingDataLength); - - var ms = new MemoryStream(); - ms.Write(magicNumber, 0, magicNumber.Length); - ms.Write(bmpSizeBytes, 0, bmpSizeBytes.Length); - ms.Write(remainingData, 0, remainingData.Length); - - // We can't just give it our input stream, - // because it would not stop at the end of the first image. - var bmp = new Bitmap(ms); yield return bmp; } } diff --git a/MovieBarCodeGenerator/ImageProcessor.cs b/MovieBarCodeGenerator/ImageProcessor.cs index 93cbe20..6f169e3 100644 --- a/MovieBarCodeGenerator/ImageProcessor.cs +++ b/MovieBarCodeGenerator/ImageProcessor.cs @@ -33,7 +33,6 @@ public class BarCodeParameters public int Width { get; set; } = 1000; public int? Height { get; set; } = null; public int BarWidth { get; set; } = 1; - public bool Smoothen { get; set; } = false; } public class ImageProcessor @@ -83,18 +82,16 @@ Graphics GetDrawingSurface(int width, int height) finalBitmapGraphics?.Dispose(); - if (parameters.Smoothen && finalBitmap != null) - { - var onePixelHeight = GetResizedImage(finalBitmap, finalBitmap.Width, 1); - var smoothened = GetResizedImage(onePixelHeight, finalBitmap.Width, finalBitmap.Height); - - onePixelHeight.Dispose(); - finalBitmap.Dispose(); + return finalBitmap; + } - finalBitmap = smoothened; + public Bitmap GetSmoothedCopy(Bitmap inputImage) + { + using (var onePixelHeight = GetResizedImage(inputImage, inputImage.Width, 1)) + { + var smoothed = GetResizedImage(onePixelHeight, inputImage.Width, inputImage.Height); + return smoothed; } - - return finalBitmap; } // https://stackoverflow.com/a/24199315/755986 diff --git a/MovieBarCodeGenerator/MainForm.Designer.cs b/MovieBarCodeGenerator/MainForm.Designer.cs index 0adf7b4..0375814 100644 --- a/MovieBarCodeGenerator/MainForm.Designer.cs +++ b/MovieBarCodeGenerator/MainForm.Designer.cs @@ -28,6 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.inputPathTextBox = new System.Windows.Forms.TextBox(); this.browseInputPathButton = new System.Windows.Forms.Button(); this.generateButton = new System.Windows.Forms.Button(); @@ -50,6 +51,7 @@ private void InitializeComponent() this.imageWidthTextBox = new System.Windows.Forms.TextBox(); this.progressBar1 = new System.Windows.Forms.ProgressBar(); this.aboutButton = new System.Windows.Forms.Button(); + this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); this.groupBox1.SuspendLayout(); this.groupBox2.SuspendLayout(); this.SuspendLayout(); @@ -164,17 +166,19 @@ private void InitializeComponent() // smoothCheckBox // this.smoothCheckBox.AutoSize = true; - this.smoothCheckBox.Location = new System.Drawing.Point(342, 39); + this.smoothCheckBox.Location = new System.Drawing.Point(349, 29); this.smoothCheckBox.Name = "smoothCheckBox"; - this.smoothCheckBox.Size = new System.Drawing.Size(85, 17); + this.smoothCheckBox.Size = new System.Drawing.Size(106, 30); this.smoothCheckBox.TabIndex = 11; - this.smoothCheckBox.Text = "Smooth bars"; + this.smoothCheckBox.Text = "Also generate\r\na smooth version"; + this.toolTip1.SetToolTip(this.smoothCheckBox, "A second image will be generated, with smoothed bars.\r\nThe file will be created n" + + "ext to the specified output path, and will have the \"_smoothed\" suffix."); this.smoothCheckBox.UseVisualStyleBackColor = true; // // label7 // this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(201, 63); + this.label7.Location = new System.Drawing.Point(190, 64); this.label7.Name = "label7"; this.label7.Size = new System.Drawing.Size(180, 13); this.label7.TabIndex = 17; @@ -183,7 +187,7 @@ private void InitializeComponent() // label6 // this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(269, 21); + this.label6.Location = new System.Drawing.Point(258, 21); this.label6.Name = "label6"; this.label6.Size = new System.Drawing.Size(54, 13); this.label6.TabIndex = 16; @@ -191,7 +195,7 @@ private void InitializeComponent() // // barWidthTextBox // - this.barWidthTextBox.Location = new System.Drawing.Point(269, 37); + this.barWidthTextBox.Location = new System.Drawing.Point(258, 37); this.barWidthTextBox.Name = "barWidthTextBox"; this.barWidthTextBox.Size = new System.Drawing.Size(55, 20); this.barWidthTextBox.TabIndex = 10; @@ -201,7 +205,7 @@ private void InitializeComponent() // label5 // this.label5.AutoSize = true; - this.label5.Location = new System.Drawing.Point(201, 21); + this.label5.Location = new System.Drawing.Point(190, 21); this.label5.Name = "label5"; this.label5.Size = new System.Drawing.Size(56, 13); this.label5.TabIndex = 14; @@ -209,7 +213,7 @@ private void InitializeComponent() // // barCountTextBox // - this.barCountTextBox.Location = new System.Drawing.Point(201, 37); + this.barCountTextBox.Location = new System.Drawing.Point(190, 37); this.barCountTextBox.Name = "barCountTextBox"; this.barCountTextBox.Size = new System.Drawing.Size(55, 20); this.barCountTextBox.TabIndex = 9; @@ -281,6 +285,12 @@ private void InitializeComponent() this.aboutButton.UseVisualStyleBackColor = true; this.aboutButton.Click += new System.EventHandler(this.aboutButton_Click); // + // toolTip1 + // + this.toolTip1.AutoPopDelay = 5000; + this.toolTip1.InitialDelay = 100; + this.toolTip1.ReshowDelay = 100; + // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -327,6 +337,7 @@ private void InitializeComponent() private System.Windows.Forms.Button aboutButton; private System.Windows.Forms.Label label7; private System.Windows.Forms.CheckBox smoothCheckBox; + private System.Windows.Forms.ToolTip toolTip1; } } diff --git a/MovieBarCodeGenerator/MainForm.cs b/MovieBarCodeGenerator/MainForm.cs index f46388d..921854f 100644 --- a/MovieBarCodeGenerator/MainForm.cs +++ b/MovieBarCodeGenerator/MainForm.cs @@ -88,10 +88,11 @@ private async void generateButton_Click(object sender, EventArgs e) string inputPath; string outputPath; + string smoothedOutputPath; BarCodeParameters parameters; try { - (inputPath, outputPath, parameters) = GetValidatedParameters(); + (inputPath, outputPath, smoothedOutputPath, parameters) = GetValidatedParameters(); } catch (OperationCanceledException) { @@ -108,7 +109,13 @@ private async void generateButton_Click(object sender, EventArgs e) var progress = new PercentageProgressHandler(percentage => { var progressBarValue = Math.Min(100, (int)Math.Round(percentage * 100, MidpointRounding.AwayFromZero)); - Invoke(new Action(() => progressBar1.Value = progressBarValue)); + Invoke(new Action(() => + { + if (_cancellationTokenSource != null) + { + progressBar1.Value = progressBarValue; + } + })); }); _cancellationTokenSource = new CancellationTokenSource(); @@ -142,14 +149,13 @@ await Task.Run(() => catch (Exception ex) { MessageBox.Show(this, - $@"Sorry, something went wrong, and it probably won't resolve on its own. + $@"Sorry, something went wrong. Here is all the info available at the time of the error (press Ctrl+C to copy it). Input: {inputPath} Output width: {parameters.Width} Output height: {parameters.Height} Bar width: {parameters.BarWidth} -Smoothen: {parameters.Smoothen} Error: {ex}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; @@ -157,7 +163,7 @@ Here is all the info available at the time of the error (press Ctrl+C to copy it finally { generateButton.Text = GenerateButtonText; - _cancellationTokenSource.Dispose(); + _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } @@ -171,36 +177,79 @@ Here is all the info available at the time of the error (press Ctrl+C to copy it { MessageBox.Show(this, $" Unable to save the image: {ex}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } + + if (smoothedOutputPath != null) + { + Bitmap smoothed; + try + { + smoothed = _imageProcessor.GetSmoothedCopy(result); + } + catch (Exception ex) + { + MessageBox.Show(this, $"An error occured while creating the smoothed version of the barcode. Error: {ex}", + "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + try + { + smoothed.Save(smoothedOutputPath); + } + catch (Exception ex) + { + MessageBox.Show(this, $" Unable to save the smoothed image: {ex}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } } - private (string InputPath, string OutputPath, BarCodeParameters Parameters) GetValidatedParameters() + private (string InputPath, string OutputPath, string SmoothedOutputPath, BarCodeParameters Parameters) + GetValidatedParameters() { - var inputPath = inputPathTextBox.Text.Trim(new [] { '"' }); + var inputPath = inputPathTextBox.Text.Trim(new[] { '"' }); if (!File.Exists(inputPath)) { throw new Exception("The input file does not exist."); } var outputPath = outputPathTextBox.Text.Trim(new[] { '"' }); - if (string.IsNullOrWhiteSpace(outputPath) || outputPath.Any(x => Path.GetInvalidPathChars().Contains(x))) - { - throw new Exception("The output path is invalid."); - } - if (!Path.HasExtension(outputPath)) + void ValidateOutputPath(ref string path) { - outputPath += ".png"; - } + if (string.IsNullOrWhiteSpace(path)) + { + path = "output.png"; + } - if (File.Exists(outputPath)) - { - var promptResult = MessageBox.Show( - $"The file '{outputPath}' already exists. Are you sure you want to overwrite it?", - "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Question); - if (promptResult != DialogResult.Yes) + if (!Path.HasExtension(path)) + { + path += ".png"; + } + + if (path.Any(x => Path.GetInvalidPathChars().Contains(x))) { - throw new OperationCanceledException(); + throw new Exception("The output path is invalid."); } + + if (File.Exists(path)) + { + var promptResult = MessageBox.Show(this, + $"The file '{path}' already exists. Do you want to overwrite it?", + "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (promptResult != DialogResult.Yes) + { + throw new OperationCanceledException(); + } + } + } + + ValidateOutputPath(ref outputPath); + + string smoothedOutputPath = null; + if (smoothCheckBox.Checked) + { + smoothedOutputPath = $"{Path.GetFileNameWithoutExtension(outputPath)}_smoothed{Path.GetExtension(outputPath)}"; + ValidateOutputPath(ref smoothedOutputPath); } if (!int.TryParse(barWidthTextBox.Text, out var barWidth)) @@ -228,13 +277,12 @@ Here is all the info available at the time of the error (press Ctrl+C to copy it var parameters = new BarCodeParameters() { - Smoothen = smoothCheckBox.Checked, BarWidth = barWidth, Width = imageWidth, Height = imageHeight }; - return (inputPath, outputPath, parameters); + return (inputPath, outputPath, smoothedOutputPath, parameters); } private void browseInputPathButton_Click(object sender, EventArgs e) diff --git a/MovieBarCodeGenerator/MainForm.resx b/MovieBarCodeGenerator/MainForm.resx index 253c29b..c24878b 100644 --- a/MovieBarCodeGenerator/MainForm.resx +++ b/MovieBarCodeGenerator/MainForm.resx @@ -147,6 +147,9 @@ True + + 17, 17 + True