6
\$\begingroup\$

I created a little tool to help me take a folder of images and convert them into a properly formatted structure for a photo gallery I'm working on. This includes keeping the original high res photo, and making a smaller version for previewing.

A gallery with 260 images takes 11,631ms on my laptop and a gallery with 10 takes about 450ms. Is there anything I can do to make this code more efficient?

Note: All photos that are sent to me for the photo gallery are a JPEG.

MainForm.cs

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;

namespace PhotoGalleryFormatter
{
    public partial class MainForm : Form
    {
        private string    _originalDirectory;
        private string    _outputDirectory;
        private string    _galleryName = "formatted-gallery";
        private string[]  _originalFiles;
        private Stopwatch _timer;

        private const double DownscaleRatio = .35;

        /// <summary>
        /// Initialize Form
        /// </summary>
        public MainForm()
        {
            InitializeComponent();
            _timer = new Stopwatch();
        }

        /// <summary>
        /// Opens FolderBrowserDialog to select original directory to work with
        /// </summary>
        private void OpenFolderBrowserDialog()
        {
            using (var dialog = new FolderBrowserDialog())
            {
                var result = dialog.ShowDialog() == DialogResult.OK;
                if (result && !string.IsNullOrWhiteSpace(dialog.SelectedPath) )
                {
                    _originalDirectory = dialog.SelectedPath;
                    _outputDirectory = _originalDirectory + @"\formatted-gallery\";
                    txtFileDir.Text = _originalDirectory;
                    txtOutputDir.Text = _outputDirectory;
                    _originalFiles = Directory.GetFiles(dialog.SelectedPath, "*.jpg");
                }

                btnFormat.Enabled = result;
            }
        }

        /// <summary>
        /// Auto-Generates a output directory based on gallery name
        /// </summary>
        private void CalculateOutputDirectory()
        {
            if ( !string.IsNullOrWhiteSpace(txtGalleryName.Text) )
            {
                _galleryName = txtGalleryName.Text;
                _galleryName = _galleryName.Replace(' ', '-');
                _outputDirectory = _originalDirectory + @"\" + _galleryName + @"\";
            }
            else
            {
                _galleryName = "formatted-gallery";
                _outputDirectory = _originalDirectory + @"\formatted-gallery\";
            }

            txtOutputDir.Text = _outputDirectory;
        }

        /// <summary>
        /// Formats gallery file names
        /// </summary>
        private void FormatGallery()
        {
            _timer.Start();

            if( Directory.Exists(_outputDirectory) )
                Directory.Delete(_outputDirectory);

            if ( Directory.Exists(_outputDirectory + @"\preview") )
                Directory.Delete(_outputDirectory + @"\preview");

            Directory.CreateDirectory(_outputDirectory);
            Directory.CreateDirectory(_outputDirectory + @"\preview");

            for (var i = 0; i < _originalFiles.Length; i++)
            {
                if ( File.Exists(_originalFiles[i]) )
                {
                    var path = _galleryName + "-" + (i + 1) + ".jpg";
                    File.Copy(_originalFiles[i], _outputDirectory + path);
                    CreatePreviewImage(path);
                }
            }

            _timer.Stop();
            MessageBox.Show("Gallery " + _galleryName + " has been formatted in " + _timer.ElapsedMilliseconds + "ms.",
                            "Gallery Format Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
            _timer.Reset();
        }

        /// <summary>
        /// Resizes image for preview folder
        /// </summary>
        /// <param name="path">Path of image</param>
        private void CreatePreviewImage(string path)
        {
            using (var originalImage = Image.FromFile(_outputDirectory + path))
            {
                using (var resizedImage = DownsizeImage(originalImage))
                {
                    resizedImage.Save(_outputDirectory + @"preview\" + path, ImageFormat.Jpeg);
                }
            }
        }

        /// <summary>
        /// Downsizes image to preview size using _downscaleRatio
        /// </summary>
        /// <param name="image">Original un-scaled image</param>
        /// <returns>New downsized image</returns>
        private Image DownsizeImage(Image image)
        {
            var newWidth  = (int) (image.Width  * DownscaleRatio);
            var newHeight = (int) (image.Height * DownscaleRatio);
            var newImage  = new Bitmap(newWidth, newHeight);

            using (var graphics = Graphics.FromImage(newImage))
            {
                graphics.DrawImage(image, 0, 0, newWidth, newHeight);
            }

            return newImage;
        }

        private void btnLoadDir_Click(object sender, EventArgs e)
        {
            OpenFolderBrowserDialog();
        }

        private void txtGalleryName_TextChanged(object sender, EventArgs e)
        {
            CalculateOutputDirectory();
        }

        private void btnFormat_Click(object sender, EventArgs e)
        {
            FormatGallery();
        }
    }
}

MainForm.Designer.cs

namespace PhotoGalleryFormatter
{
    partial class MainForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
            this.btnLoadDir = new System.Windows.Forms.Button();
            this.txtFileDir = new System.Windows.Forms.TextBox();
            this.gbFileInfo = new System.Windows.Forms.GroupBox();
            this.btnFormat = new System.Windows.Forms.Button();
            this.lblOutputDir = new System.Windows.Forms.Label();
            this.txtOutputDir = new System.Windows.Forms.TextBox();
            this.lblOriginalDir = new System.Windows.Forms.Label();
            this.txtGalleryName = new System.Windows.Forms.TextBox();
            this.lblGalleryName = new System.Windows.Forms.Label();
            this.gbFileInfo.SuspendLayout();
            this.SuspendLayout();
            // 
            // btnLoadDir
            // 
            this.btnLoadDir.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.btnLoadDir.Location = new System.Drawing.Point(6, 188);
            this.btnLoadDir.Name = "btnLoadDir";
            this.btnLoadDir.Size = new System.Drawing.Size(196, 38);
            this.btnLoadDir.TabIndex = 0;
            this.btnLoadDir.Text = "Load Directory";
            this.btnLoadDir.UseVisualStyleBackColor = true;
            this.btnLoadDir.Click += new System.EventHandler(this.btnLoadDir_Click);
            // 
            // txtFileDir
            // 
            this.txtFileDir.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.txtFileDir.Location = new System.Drawing.Point(9, 48);
            this.txtFileDir.Name = "txtFileDir";
            this.txtFileDir.ReadOnly = true;
            this.txtFileDir.Size = new System.Drawing.Size(409, 22);
            this.txtFileDir.TabIndex = 1;
            // 
            // gbFileInfo
            // 
            this.gbFileInfo.Controls.Add(this.btnFormat);
            this.gbFileInfo.Controls.Add(this.lblOutputDir);
            this.gbFileInfo.Controls.Add(this.txtOutputDir);
            this.gbFileInfo.Controls.Add(this.lblOriginalDir);
            this.gbFileInfo.Controls.Add(this.txtGalleryName);
            this.gbFileInfo.Controls.Add(this.lblGalleryName);
            this.gbFileInfo.Controls.Add(this.txtFileDir);
            this.gbFileInfo.Controls.Add(this.btnLoadDir);
            this.gbFileInfo.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.gbFileInfo.Location = new System.Drawing.Point(12, 12);
            this.gbFileInfo.Name = "gbFileInfo";
            this.gbFileInfo.Size = new System.Drawing.Size(424, 238);
            this.gbFileInfo.TabIndex = 2;
            this.gbFileInfo.TabStop = false;
            this.gbFileInfo.Text = "File Info";
            // 
            // btnFormat
            // 
            this.btnFormat.Enabled = false;
            this.btnFormat.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.btnFormat.Location = new System.Drawing.Point(222, 188);
            this.btnFormat.Name = "btnFormat";
            this.btnFormat.Size = new System.Drawing.Size(196, 38);
            this.btnFormat.TabIndex = 8;
            this.btnFormat.Text = "Format Gallery";
            this.btnFormat.UseVisualStyleBackColor = true;
            this.btnFormat.Click += new System.EventHandler(this.btnFormat_Click);
            // 
            // lblOutputDir
            // 
            this.lblOutputDir.AutoSize = true;
            this.lblOutputDir.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblOutputDir.Location = new System.Drawing.Point(6, 140);
            this.lblOutputDir.Name = "lblOutputDir";
            this.lblOutputDir.Size = new System.Drawing.Size(112, 17);
            this.lblOutputDir.TabIndex = 7;
            this.lblOutputDir.Text = "Output Directory";
            // 
            // txtOutputDir
            // 
            this.txtOutputDir.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.txtOutputDir.Location = new System.Drawing.Point(6, 160);
            this.txtOutputDir.Name = "txtOutputDir";
            this.txtOutputDir.ReadOnly = true;
            this.txtOutputDir.Size = new System.Drawing.Size(412, 22);
            this.txtOutputDir.TabIndex = 6;
            // 
            // lblOriginalDir
            // 
            this.lblOriginalDir.AutoSize = true;
            this.lblOriginalDir.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblOriginalDir.Location = new System.Drawing.Point(6, 28);
            this.lblOriginalDir.Name = "lblOriginalDir";
            this.lblOriginalDir.Size = new System.Drawing.Size(118, 17);
            this.lblOriginalDir.TabIndex = 4;
            this.lblOriginalDir.Text = "Original Directory";
            // 
            // txtGalleryName
            // 
            this.txtGalleryName.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.txtGalleryName.Location = new System.Drawing.Point(9, 105);
            this.txtGalleryName.Name = "txtGalleryName";
            this.txtGalleryName.Size = new System.Drawing.Size(409, 22);
            this.txtGalleryName.TabIndex = 3;
            this.txtGalleryName.TextChanged += new System.EventHandler(this.txtGalleryName_TextChanged);
            // 
            // lblGalleryName
            // 
            this.lblGalleryName.AutoSize = true;
            this.lblGalleryName.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblGalleryName.Location = new System.Drawing.Point(6, 85);
            this.lblGalleryName.Name = "lblGalleryName";
            this.lblGalleryName.Size = new System.Drawing.Size(94, 17);
            this.lblGalleryName.TabIndex = 2;
            this.lblGalleryName.Text = "Gallery Name";
            // 
            // MainForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(234)))), ((int)(((byte)(234)))), ((int)(((byte)(234)))));
            this.ClientSize = new System.Drawing.Size(447, 262);
            this.Controls.Add(this.gbFileInfo);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
            this.MaximizeBox = false;
            this.Name = "MainForm";
            this.Text = "Photo Gallery Formatter";
            this.gbFileInfo.ResumeLayout(false);
            this.gbFileInfo.PerformLayout();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Button btnLoadDir;
        private System.Windows.Forms.TextBox txtFileDir;
        private System.Windows.Forms.GroupBox gbFileInfo;
        private System.Windows.Forms.Button btnFormat;
        private System.Windows.Forms.Label lblOutputDir;
        private System.Windows.Forms.TextBox txtOutputDir;
        private System.Windows.Forms.Label lblOriginalDir;
        private System.Windows.Forms.TextBox txtGalleryName;
        private System.Windows.Forms.Label lblGalleryName;
    }
}
\$\endgroup\$
6
  • 1
    \$\begingroup\$ Copy and then read from file seems like extra work. Read the image, downsize then save to a new directory. Delete directory seems kind of reckless. \$\endgroup\$
    – paparazzo
    Aug 10, 2017 at 14:05
  • 1
    \$\begingroup\$ @Paparazzi The reason I delete it is because I don't want a duplicate. However, asking if the user wants to overwrite would be a better solution. \$\endgroup\$ Aug 10, 2017 at 14:11
  • \$\begingroup\$ Are the files on your hard drive? Why do you need to copy the original? \$\endgroup\$
    – paparazzo
    Aug 10, 2017 at 16:17
  • \$\begingroup\$ @Paparazzi Files start out on a hard drive. I plan to have it upload to a server instead of copying itself in the next iteration. \$\endgroup\$ Aug 10, 2017 at 16:23
  • \$\begingroup\$ I still think you are doing extra work read it, copy original, process, then copy processed. \$\endgroup\$
    – paparazzo
    Aug 10, 2017 at 16:25

3 Answers 3

4
\$\begingroup\$

this doesn't help with time but does help with code simplicity

    private void CreatePreviewImage(string path)
    {
        using (var originalImage = Image.FromFile(_outputDirectory + path))
        {
            using (var resizedImage = DownsizeImage(originalImage))
            {
                resizedImage.Save(_outputDirectory + @"preview\" + path, ImageFormat.Jpeg);
            }
        }
    }

you only need to indent once here and use a single set of brackets, like this:

private void CreatePreviewImage(string path)
{
    using (var originalImage = Image.FromFile(_outputDirectory + path))
    using (var resizedImage = DownsizeImage(originalImage))
    {
        resizedImage.Save(_outputDirectory + @"preview\" + path, ImageFormat.Jpeg);
    }

}

you could also put both resources into a single using statement as @Vogel612 suggests in his comment, like this:

private void CreatePreviewImage(string path)
{
    using (var originalImage = Image.FromFile(_outputDirectory + path), 
            var resizedImage = DownsizeImage(originalImage))
    {
        resizedImage.Save(_outputDirectory + @"preview\" + path, ImageFormat.Jpeg);
    }      
}

Just make sure that the resources are in the right order.


you should always be in the habit of deleting the child folder before the parent folder. I would switch these around:

if( Directory.Exists(_outputDirectory) )
    Directory.Delete(_outputDirectory);

if ( Directory.Exists(_outputDirectory + @"\preview") )
    Directory.Delete(_outputDirectory + @"\preview");

you should change the incrementation variable to start at 1 instead of 0 because you are using it for naming purposes only, I would also use string interpolation, (I assume that you are going to be using C# 6+), all of this makes assigning the path look like this:

var path = $@"{_galleryName}-{i}.jpg";

personally I would change this to a foreach loop though, and it would look like this instead (I wasn't thinking about the numbering system...).

int i = 1;
foreach(var file in _originalFiles)
{
    if (File.Exists(file))
    {
        var path = $@"{_galleryName}-{i}.jpg
        File.Copy(file, _outputDirectory + path)
        CreatePreviewImage(path);
        i++;
    }
}

I don't know off the top of my head if this will increase the speed of going through the files or not, but I would definitely check it out to see.

random thought

I have issues with the numbering system because then the pictures will have a weird sort by default in most folder viewing applications, once you hit 10 they sort weird and if everything has the same creation date you can't sort it.


you should look into using String Interpolation, it makes a lot of your string concatenation look so much cleaner.

this:

_outputDirectory = _originalDirectory + @"\" + _galleryName + @"\";

becomes this:

_outputDirectory = $@"{_originalDirectory}\{_galleryName}\";

When needing backslashes in the string you need to include the @ symbol, order is important.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Don't forget to use $@ before string for _outputDirectory since backslash means escape sequence without @. \$\endgroup\$
    – Maxim
    Aug 10, 2017 at 14:53
  • 1
    \$\begingroup\$ Really good answer. I had no idea about string interpolation until now. Also about your random thought, I'm formatting these for an already existing photo gallery on a website. I just used the same scheme as the original developer. Thank you though. \$\endgroup\$ Aug 10, 2017 at 14:58
  • \$\begingroup\$ thank you @Maxim, I completely forgot about the Backslashes causing issues.... \$\endgroup\$
    – Malachi
    Aug 10, 2017 at 15:00
  • 1
    \$\begingroup\$ N.B. that you can collapse the double using into a single one. C# supports multiple resources in one using-statement and will clean them up in the correct order. \$\endgroup\$
    – Vogel612
    Aug 11, 2017 at 13:19
5
\$\begingroup\$

You copy to disk then read from disk

File.Copy(_originalFiles[i], _outputDirectory + path);

var originalImage = Image.FromFile(_outputDirectory + path)

Read to memory, copy to disk, load the image from memory. It saves a read from file.

Image.FromStream

\$\endgroup\$
1
  • \$\begingroup\$ Don't know why I didn't think of that, I'll try it out. Good call \$\endgroup\$ Aug 10, 2017 at 16:38
4
\$\begingroup\$

Just small note about combining path parts. You should never combine them manually with backslashes. There is the Path.Combine method and you should always use it.

Instead of

_outputDirectory = _originalDirectory + @"\" + _galleryName + @"\";

you should write

Path.Combine(_originalDirectory, _galleryName);

Instead of

_outputDirectory + @"\preview"

write

Path.Combine(_outputDirectory, "preview")

And here

File.Copy(_originalFiles[i], _outputDirectory + path);

write

File.Copy(_originalFiles[i], Path.Combine(_outputDirectory, path));
\$\endgroup\$
2
  • \$\begingroup\$ After doing some testing I've found that they run about 20% slower but the Path.Combine really helps make the code look better so I think it's worth it. Thank you. \$\endgroup\$ Aug 10, 2017 at 15:58
  • 3
    \$\begingroup\$ Path.Combine is something more than just strings concatenation. You can look at the source code of this method on Reference Source. As you can see the method perform checks on invalid chars, utilize path separators and takes care about these separators at the end of each string. \$\endgroup\$
    – Maxim
    Aug 11, 2017 at 1:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.